[
  {
    "path": ".allstar/binary_artifacts.yaml",
    "content": "# Ignore reason: jars are used for testing purposes only\nignorePaths:\n  - jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/libs/dependency-1.0.0.jar\n  - jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/libs/dependency-1.0.0.jar"
  },
  {
    "path": ".gitattributes",
    "content": "*.bat\teol=crlf\n*.cmd\teol=crlf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue_report.md",
    "content": "---\nname: Issue report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n<!--\nPlease follow the guidelines below before opening an issue:\n1. Ensure the issue was not already reported. \n2. Open a new issue if you are unable to find an existing issue addressing your problem. Make sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.\n3. Discuss the priority and potential solutions with the maintainers in the issue. The maintainers would review the issue and add a label \"Accepting Contributions\" once the issue is ready for accepting contributions. \n4. Open a PR only if the issue is labeled with \"Accepting Contributions\", ensure the PR description clearly describes the problem and solution. Note that an open PR without an issues labeled with \"Accepting Contributions\" will not be accepted.\n\nPlease reproduce with the latest version of Jib.\n\nIf you are encountering errors pulling or pushing to remote registries,\nplease check our Frequently Asked Questions before filing an issue:\n\n    https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#registry-errors\n-->\n\n**Environment**:\n\n- *Jib version:* \n- *Build tool:* <!-- Maven/Gradle, including version -->\n- *OS:* \n\n**Description of the issue**:\n\n\n**Expected behavior**:\n\n\n**Steps to reproduce**:\n<!-- Please provide a minimal and precise series of steps -->\n\n  1. \n  2. \n  3. \n\n**`jib-maven-plugin` Configuration**: <!-- Delete this section if not used -->\n```xml\nPASTE YOUR pom.xml CONFIGURATION HERE\n```\n\n**`jib-gradle-plugin` Configuration**: <!-- Delete this section if not used -->\n```groovy\nPASTE YOUR build.gradle CONFIGURATION HERE\n```\n\n**Log output**: <!-- If applicable, provide relevant log output -->\n\n**Additional Information**: <!-- Any additional information that may be helpful -->\n\n\n<!-- Thanks for contributing! -->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "Thank you for your interest in contributing! For general guidelines, please refer to\nthe [contributing guide](https://github.com/GoogleContainerTools/jib/blob/master/CONTRIBUTING.md).\n\nPlease follow the guidelines below before opening an issue or a PR:\n- [ ] Ensure the issue was not already reported. \n- [ ] Create a new issue at https://github.com/GoogleContainerTools/jib/issues/new/choose if you are unable to find an existing issue addressing your problem. Make sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.\n- [ ] Discuss the priority and potential solutions with the maintainers in the issue. The maintainers would review the issue and add a label \"Accepting Contributions\" once the issue is ready for accepting contributions.\n- [ ] Open a PR only if the issue is labeled with \"Accepting Contributions\", ensure the PR description clearly describes the problem and solution. Note that an open PR without an issues labeled with \"Accepting Contributions\" will not be accepted.\n- [ ] Verify that integration tests and unit tests are passing after the change.\n- [ ] Address all checkstyle issues. Refer to the [style guide](https://github.com/GoogleContainerTools/jib/blob/master/STYLE_GUIDE.md).\n\nFixes #<issue_number_goes_here> 🛠️\n"
  },
  {
    "path": ".github/RELEASE_TEMPLATES/cli_release_checklist.md",
    "content": "---\ntitle: CLI Release {{ env.RELEASE_NAME }}\nlabels: release\n---\n## Requirements\n- [ ] ⚠️ Ensure the release process has succeeded before proceeding\n- [ ] ⚠️ Publish [Release]({{ env.RELEASE_DRAFT }}) after adding CHANGELOG entries ([example](https://github.com/GoogleContainerTools/jib/releases/tag/v0.8.0-cli))\n\n## GCS\n- [ ] Run {{ env.GCS_UPDATE_SCRIPT }} script to update GCS with the latest version number\n\n## Github\n- [ ] Update [CHANGELOG.md]({{ env.CHANGELOG_URL }})\n- [ ] Update [README.md]({{ env.README_URL }})\n- [ ] Merge PR ({{ env.RELEASE_PR }})\n"
  },
  {
    "path": ".github/RELEASE_TEMPLATES/core_release_checklist.md",
    "content": "---\ntitle: Core Release {{ env.RELEASE_NAME }}\nlabels: release\n---\n## Requirements\n- [ ] ⚠️ Ensure the release process has succeeded before proceeding\n\n## Github\n- [ ] Update [CHANGELOG.md]({{ env.CHANGELOG_URL }})\n- [ ] Update [README.md]({{ env.README_URL }})\n- [ ] Publish [Release]({{ env.RELEASE_DRAFT }})\n- [ ] Merge PR ({{ env.RELEASE_PR }})\n"
  },
  {
    "path": ".github/RELEASE_TEMPLATES/plugin_release_checklist.md",
    "content": "---\ntitle: Plugin Release {{ env.RELEASE_NAME }}\nlabels: release\n---\n## Requirements\n- [ ] ⚠️ Ensure the release process has succeeded before proceeding\n\n## GCS\n- [ ] Run {{ env.GCS_UPDATE_SCRIPT }} script to update GCS with the latest version number\n\n## Github\n- [ ] Update [CHANGELOG.md]({{ env.CHANGELOG_URL }})\n- [ ] Update [README.md]({{ env.README_URL }})\n- [ ] Search/replace the old published version with the new published version in [examples](https://github.com/GoogleContainerTools/jib/tree/master/examples)\n- [ ] Publish [Release]({{ env.RELEASE_DRAFT }})\n- [ ] Merge PR ({{ env.RELEASE_PR }})\n\n#### Jib-Extensions\n- [ ] Update versions in [Jib Extensions](https://github.com/GoogleContainerTools/jib-extensions)\n- [ ] If there were Gradle API or Jib API changes, double-check compatibility and update Version Matrix on jib-extensions. It may require re-releasing first-party extensions. See [jib-extensions#45](https://github.com/GoogleContainerTools/jib-extensions/pull/45), [jib-extensions#44](https://github.com/GoogleContainerTools/jib-extensions/pull/44), and [jib-extensions#42](https://github.com/GoogleContainerTools/jib-extensions/pull/42)\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gradle\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/gradle-wrapper-validation.yml",
    "content": "name: \"Validate Gradle Wrapper\"\non: [push, pull_request]\n\njobs:\n  validation:\n    name: \"Gradle wrapper validation\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: gradle/wrapper-validation-action@v3.5.0\n"
  },
  {
    "path": ".github/workflows/jib-cli-release.yml",
    "content": "name: Release Jib CLI\non:\n  workflow_dispatch:\n    inputs:\n      release_version:\n        description: new release version\n        required: true\n        default: (for example, 0.1.0)\n\npermissions:\n  contents: write\n  issues: write\n\njobs:\n  release:\n    name: Release Jib CLI\n    runs-on: ubuntu-latest\n    outputs:\n      hashes: ${{ steps.hash.outputs.hashes }}\n      upload_url: ${{ steps.create-release.outputs.upload_url }}\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v4\n\n      - name: Set up JDK 8\n        uses: actions/setup-java@v3\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n\n      - name: Build project\n        run: |\n          if [[ ! \"${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n            echo 'version \"${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\" not in ###.###.### format'\n            exit 1\n          fi\n          # TODO: run integration test? (Requries auth with GCP.)\n          ./gradlew clean build --stacktrace\n        env:\n          GITHUB_EVENT_INPUTS_RELEASE_VERSION: ${{ github.event.inputs.release_version }}\n\n      - name: Run Gradle release\n        run: |\n          git checkout -b cli-release-v${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\n          git config user.email ${GITHUB_ACTOR}@users.noreply.github.com\n          git config user.name ${GITHUB_ACTOR}\n          # This creates the tag (e.g., \"v0.1.0-cli\") and pushes the updated\n          # branch (e.g., \"cli-release-v0.1.0\") and the new tag.\n          ./gradlew jib-cli:release \\\n            -Prelease.useAutomaticVersion=true \\\n            -Prelease.releaseVersion=${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\n        env:\n          GITHUB_EVENT_INPUTS_RELEASE_VERSION: ${{ github.event.inputs.release_version }}\n\n      - name: Build Jib CLI release binaries\n        run: |\n          git checkout v${GITHUB_EVENT_INPUTS_RELEASE_VERSION}-cli\n          ./gradlew jib-cli:instDist --stacktrace\n\n          cd jib-cli/build/distributions\n          mv jib-${GITHUB_EVENT_INPUTS_RELEASE_VERSION}.zip jib-jre-${GITHUB_EVENT_INPUTS_RELEASE_VERSION}.zip\n          sha256sum jib-jre-${GITHUB_EVENT_INPUTS_RELEASE_VERSION}.zip > zip.sha256\n        env:\n          GITHUB_EVENT_INPUTS_RELEASE_VERSION: ${{ github.event.inputs.release_version }}\n\n      - name: Generate SLSA subject for Jib CLI release binaries\n        id: hash\n        working-directory: jib-cli/build/distributions\n        run: echo \"hashes=$(cat zip.sha256 | base64 -w0)\" >> $GITHUB_OUTPUT\n\n      - name: Create pull request\n        uses: repo-sync/pull-request@v2.12.1\n        id: create-pr\n        with:\n          github_token: ${{ secrets.CLOUD_JAVA_BOT_GITHUB_TOKEN }}\n          source_branch: cli-release-v${{ github.event.inputs.release_version }}\n          pr_title: \"CLI release v${{ github.event.inputs.release_version }}\"\n          pr_body: \"To be merged after the release is complete.\"\n          pr_label: \"PR: Merge After Release\"\n\n      - name: Draft GitHub release\n        uses: actions/create-release@v1.1.4\n        id: create-release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: v${{ github.event.inputs.release_version }}-cli\n          release_name: jib-cli v${{ github.event.inputs.release_version }}\n          draft: true\n          body: |\n            ### Major Changes\n\n            See [CHANGELOG.md](https://github.com/GoogleContainerTools/jib/blob/master/jib-cli/CHANGELOG.md) for more details.\n\n      - name: Upload Jib CLI\n        uses: actions/upload-release-asset@v1.0.2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create-release.outputs.upload_url }}\n          asset_path: ./jib-cli/build/distributions/jib-jre-${{ github.event.inputs.release_version }}.zip\n          asset_name: jib-jre-${{ github.event.inputs.release_version }}.zip\n          asset_content_type: application/zip\n\n      - name: Upload Jib CLI checksum\n        uses: actions/upload-release-asset@v1.0.2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create-release.outputs.upload_url }}\n          asset_path: ./jib-cli/build/distributions/zip.sha256\n          asset_name: jib-jre-${{ github.event.inputs.release_version }}.zip.sha256\n          asset_content_type: text/plain\n\n      - name: Create Jib CLI release checklist issue\n        uses: JasonEtco/create-an-issue@v2.9.2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          RELEASE_NAME: v${{ github.event.inputs.release_version }}-cli\n          CHANGELOG_URL: https://github.com/GoogleContainerTools/jib/blob/master/jib-cli/CHANGELOG.md\n          README_URL: https://github.com/GoogleContainerTools/jib/blob/master/jib-cli/README.md\n          GCS_UPDATE_SCRIPT: \"`./jib-cli/scripts/update_gcs_latest.sh ${{ github.event.inputs.release_version }}`\"\n          RELEASE_DRAFT: ${{ steps.create-release.outputs.html_url }}\n          RELEASE_PR: ${{steps.create-pr.outputs.pr_url}}\n        with:\n          filename: .github/RELEASE_TEMPLATES/cli_release_checklist.md\n\n  provenance:\n    needs: [release]\n    permissions:\n      actions: read # To read the workflow path.\n      id-token: write # To sign the provenance.\n      contents: write # To add assets to a release.\n    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0\n    with:\n      base64-subjects: \"${{ needs.release.outputs.hashes }}\"\n\n  upload:\n    needs: [release, provenance]\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download attestation\n        uses: actions/download-artifact@v3\n        with:\n          name: \"${{ needs.provenance.outputs.attestation-name }}\"\n\n      - uses: actions/upload-release-asset@v1.0.2\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ needs.release.outputs.upload_url }}\n          asset_path: \"${{ needs.provenance.outputs.attestation-name }}\"\n          asset_name: \"${{ needs.provenance.outputs.attestation-name }}\"\n          asset_content_type: application/json\n"
  },
  {
    "path": ".github/workflows/prepare-release.yml",
    "content": "name: Prepare Jib release\non:\n  workflow_dispatch:\n    inputs:\n      project:\n        description: Jib project to release\n        required: true\n        default: (build-plan | core | maven | gradle | extension-common | maven-extension | gradle-extension)\n      release_version:\n        description: new release version\n        required: true\n        default: (for example, 0.1.0)\n\npermissions:\n  contents: write\n  issues: write\n\njobs:\n  release:\n    name: Prepare Jib release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v4\n      - name: Set up JDK 8\n        uses: actions/setup-java@v3\n        with:\n          java-version: '8'\n          distribution: 'temurin'\n      - name: Check input\n        run: |\n          echo '* input project: \"${GITHUB_EVENT_INPUTS_PROJECT}\"'\n\n          case ${GITHUB_EVENT_INPUTS_PROJECT} in\n            build-plan|core|maven|gradle|extension-common|maven-extension|gradle-extension) ;;\n            *) echo 'invalid input project name \"${GITHUB_EVENT_INPUTS_PROJECT}\"'\n               exit 1\n               ;;\n          esac\n          if [[ ! \"${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n            echo 'version \"${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\" not in ###.###.### format'\n            exit 1\n          fi\n        env:\n          GITHUB_EVENT_INPUTS_PROJECT: ${{ github.event.inputs.project }}\n          GITHUB_EVENT_INPUTS_RELEASE_VERSION: ${{ github.event.inputs.release_version }}\n\n      - name: Build project\n        run: |\n          # TODO: run integration test? (Requries auth with GCP.)\n          ./gradlew clean build --stacktrace\n\n      - name: Run Gradle release\n        run: |\n          git checkout -b ${GITHUB_EVENT_INPUTS_PROJECT}-release-v${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\n          git config user.email ${GITHUB_ACTOR}@users.noreply.github.com\n          git config user.name ${GITHUB_ACTOR}\n\n          PROJECT=$( case ${GITHUB_EVENT_INPUTS_PROJECT} in\n            extension-common) echo jib-plugins-extension-common ;;\n            maven-extension)  echo jib-maven-plugin-extension-api ;;\n            gradle-extension) echo jib-gradle-plugin-extension-api ;;\n            maven|gradle)     echo jib-${GITHUB_EVENT_INPUTS_PROJECT}-plugin ;;\n            *)                echo jib-${GITHUB_EVENT_INPUTS_PROJECT} ;;\n          esac )\n          # This creates the tag (e.g., \"v0.1.0-gradle\") and pushes the updated\n          # branch (e.g., \"gradle-release-v0.1.0\") and the new tag.\n          ./gradlew \"${PROJECT}\":release \\\n            -Prelease.useAutomaticVersion=true \\\n            -Prelease.releaseVersion=${GITHUB_EVENT_INPUTS_RELEASE_VERSION}\n        env:\n          GITHUB_EVENT_INPUTS_PROJECT: ${{ github.event.inputs.project }}\n          GITHUB_EVENT_INPUTS_RELEASE_VERSION: ${{ github.event.inputs.release_version }}\n\n      - name: Create pull request\n        uses: repo-sync/pull-request@v2.12.1\n        id: create-pr\n        with:\n          github_token: ${{ secrets.CLOUD_JAVA_BOT_GITHUB_TOKEN }}\n          source_branch: ${{ github.event.inputs.project }}-release-v${{ github.event.inputs.release_version }}\n          pr_title: \"${{ github.event.inputs.project }} release v${{ github.event.inputs.release_version }}\"\n          pr_body: \"To be merged after the release is complete.\"\n          pr_label: \"PR: Merge After Release\"\n\n      - name: Draft Maven/Gradle GitHub release\n        uses: actions/create-release@v1.1.4\n        id: create-plugin-release\n        if: ${{ github.event.inputs.project == 'maven' || github.event.inputs.project == 'gradle' }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: v${{ github.event.inputs.release_version }}-${{ github.event.inputs.project }}\n          release_name: jib-${{ github.event.inputs.project }}-plugin v${{ github.event.inputs.release_version }}\n          draft: true\n          body: |\n            **Run `./jib-${{ github.event.inputs.project }}-plugin/scripts/update_gcs_latest.sh ${{ github.event.inputs.release_version }}`\n            when the release is complete to update the latest version string on GCS.**\n\n            ---\n            ### Major Changes\n\n            See [CHANGELOG.md](https://github.com/GoogleContainerTools/jib/blob/master/jib-${{ github.event.inputs.project }}-plugin/CHANGELOG.md) for more details.\n\n      - name: Create Maven/Gradle release checklist issue\n        uses: JasonEtco/create-an-issue@v2.9.2\n        if: ${{ github.event.inputs.project == 'maven' || github.event.inputs.project == 'gradle' }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          RELEASE_NAME: v${{ github.event.inputs.release_version }}-${{ github.event.inputs.project }}\n          CHANGELOG_URL: https://github.com/GoogleContainerTools/jib/blob/master/jib-${{ github.event.inputs.project }}-plugin/CHANGELOG.md\n          README_URL: https://github.com/GoogleContainerTools/jib/blob/master/jib-${{ github.event.inputs.project }}-plugin/README.md\n          GCS_UPDATE_SCRIPT: \"`./jib-${{ github.event.inputs.project }}-plugin/scripts/update_gcs_latest.sh ${{ github.event.inputs.release_version }}`\"\n          RELEASE_DRAFT: ${{ steps.create-plugin-release.outputs.html_url }}\n          RELEASE_PR: ${{steps.create-pr.outputs.pr_url}}\n        with:\n          filename: .github/RELEASE_TEMPLATES/plugin_release_checklist.md\n\n      - name: Draft Core GitHub release\n        uses: actions/create-release@v1.1.4\n        id: create-core-release\n        if: ${{ github.event.inputs.project == 'core' }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: v${{ github.event.inputs.release_version }}-core\n          release_name: jib-core v${{ github.event.inputs.release_version }}\n          draft: true\n          body: |\n            ### Major Changes\n\n            See [CHANGELOG.md](https://github.com/GoogleContainerTools/jib/blob/master/jib-core/CHANGELOG.md) for more details.\n\n      - name: Create Core release checklist issue\n        uses: JasonEtco/create-an-issue@v2.9.2\n        if: ${{ github.event.inputs.project == 'core' }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          RELEASE_NAME: v${{ github.event.inputs.release_version }}-core\n          CHANGELOG_URL: https://github.com/GoogleContainerTools/jib/blob/master/jib-core/CHANGELOG.md\n          README_URL: https://github.com/GoogleContainerTools/jib/blob/master/jib-core/README.md\n          RELEASE_DRAFT: ${{ steps.create-core-release.outputs.html_url }}\n          RELEASE_PR: ${{steps.create-pr.outputs.pr_url}}\n        with:\n          filename: .github/RELEASE_TEMPLATES/core_release_checklist.md\n"
  },
  {
    "path": ".github/workflows/sonar.yml",
    "content": "name: SonarCloud Analysis\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    types: [opened, synchronize, reopened]\n  workflow_dispatch:\n  schedule:\n    - cron: '00 6 * * *' # 06:00 UTC every day\n\njobs:\n  sonar:\n    if: github.repository == 'GoogleContainerTools/jib' # Only run on upstream branch\n    name: Build with Sonar\n    runs-on: ubuntu-20.04\n    steps:\n      - name: Get current date\n        id: date\n        run: echo \"date=$(date +'%Y-%m-%d' --utc)\" >> $GITHUB_OUTPUT\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis\n      - name: Set up JDK 11\n        uses: actions/setup-java@v3\n        with:\n          distribution: 'adopt'\n          java-version: 11\n      - name: Cache SonarCloud packages\n        uses: actions/cache@v4\n        with:\n          path: ~/.sonar/cache\n          key: ${{ runner.os }}-sonar-${{ steps.date.outputs.date }}\n      - uses: actions/cache@v4\n        with:\n          path: |\n            ~/.m2/repository\n            ~/.gradle/caches\n            ~/.gradle/wrapper\n          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}\n          restore-keys: |\n            ${{ runner.os }}-gradle-\n      - name: Test w/ coverage\n        continue-on-error: true\n        run: |\n          ./gradlew clean build jacocoTestReport --stacktrace\n      - name: Build and analyze\n        continue-on-error: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n        run: |\n          ./gradlew sonarqube --stacktrace\n"
  },
  {
    "path": ".github/workflows/unit-tests.yml",
    "content": "name: Unit Tests\non:\n  push:\n    branches:\n      - master\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  unit-tests:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        java: [8, 11]\n    env:\n      # for gradle\n      TERM: dumb\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 2\n      - uses: actions/setup-java@v3\n        with:\n          distribution: 'adopt'\n          java-version: ${{ matrix.java }}\n      - uses: actions/cache@v4\n        with:\n          path: |\n            ~/.m2/repository\n            ~/.gradle/caches\n            ~/.gradle/wrapper\n          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}\n          restore-keys: |\n            ${{ runner.os }}-gradle-\n      - name: Run tests\n        run: |\n          ./gradlew clean build jacocoTestReport --stacktrace\n"
  },
  {
    "path": ".gitignore",
    "content": "build/\n!jib-gradle-plugin/src/test/resources/gradle/application/build/\ntarget/\nout\nbin/\n*.iml\n*.ipr\n*.iws\n.idea\n.gradle/\n# https://github.com/takari/maven-wrapper#usage-without-binary-jar\n**/.mvn/wrapper/maven-wrapper.jar\n.settings/\n.classpath\n.project\n.DS_Store\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project,\nand in the interest of fostering an open and welcoming community,\nwe pledge to respect all people who contribute through reporting issues,\nposting feature requests, updating documentation,\nsubmitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project\na harassment-free experience for everyone,\nregardless of level of experience, gender, gender identity and expression,\nsexual orientation, disability, personal appearance,\nbody size, race, ethnicity, age, religion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery\n* Personal attacks\n* Trolling or insulting/derogatory comments\n* Public or private harassment\n* Publishing other's private information,\nsuch as physical or electronic\naddresses, without explicit permission\n* Other unethical or unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct.\nBy adopting this Code of Conduct,\nproject maintainers commit themselves to fairly and consistently\napplying these principles to every aspect of managing this project.\nProject maintainers who do not follow or enforce the Code of Conduct\nmay be permanently removed from the project team.\n\nThis code of conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior\nmay be reported by opening an issue\nor contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,\navailable at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "\nThis project is currently stable, and we are primarily focused on critical bug fixes and platform evolution to ensure it continues to work for its supported use cases.\n\n# Contributing to Jib\n\nPlease follow the guidelines below before opening an issue or a PR:\n1. Ensure the issue was not already reported. \n2. Open a new issue if you are unable to find an existing issue addressing your problem. Make sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.\n3. Discuss the priority and potential solutions with the maintainers in the issue. The maintainers would review the issue and add a label \"Accepting Contributions\" once the issue is ready for accepting contributions. \n4. Open a PR only if the issue is labeled with \"Accepting Contributions\", ensure the PR description clearly describes the problem and solution. Note that an open PR without an issues labeled with \"Accepting Contributions\" will not be accepted.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution;\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Code Reviews\n\nAll submissions, including submissions by project members, require review. We\nuse Github pull requests for this purpose.\n\nBefore submitting a pull request, please make sure to:\n\n- Identify an existing [issue](https://github.com/GoogleContainerTools/jib/issues) to associate\n  with your proposed change, or [file a new issue](https://github.com/GoogleContainerTools/jib/issues/new).\n- Describe any implementation plans in the issue and wait for a review from the repository maintainers.\n\n### Typical Contribution Cycle\n\n1. Set your git user.email property to the address used for signing the CLA. E.g.\n   ```\n   git config --global user.email \"janedoe@google.com\"\n   ```\n   If you're a Googler or other corporate contributor,\n   use your corporate email address here, not your personal address.\n2. Fork the repository into your own Github account.\n3. We follow our own [Java style guide](STYLE_GUIDE.md) that extends the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html).\n3. Please include unit tests (and integration tests if applicable) for all new code.\n4. Make sure all existing tests pass (but see the note below about integration tests).\n   * run `./gradlew clean goJF build integrationTest`\n5. Associate the change with an existing issue or file a [new issue](../../issues).\n6. Create a pull request!\n\n## Building Jib\n\nJib comes as 3 public components:\n\n- `jib-core`: a library for building containers\n- `jib-maven-plugin`: a Maven plugin that uses `jib-core` and `jib-plugins-common`\n- `jib-gradle-plugin`: a Gradle plugin that uses `jib-core` and `jib-plugins-common`\n\nAnd 1 internal component:\n\n- `jib-plugins-common`: a library with helpers for maven/gradle plugins\n\nThe project is configured as a single gradle build. Run `./gradlew build` to build the\nwhole project. Run `./gradlew install` to install all public components into the\nlocal maven repository.\n\n### Integration Tests\n**Note** that in order to run integration tests, you will need to set one of the\nfollowing environment variables:\n\n  - If you are using a GCP project then set `JIB_INTEGRATION_TESTING_PROJECT` to the GCP project to use for testing;\n    the registry tested will be `gcr.io/<JIB_INTEGRATION_TESTING_PROJECT>`.\n    - Configure authentication to Container Registry by following these [steps](https://cloud.google.com/container-registry/docs/advanced-authentication).\n    - Enable the Google Container Registry API [here](https://console.cloud.google.com/apis/library/containerregistry.googleapis.com).\n  - If you're not using a GCP project then set `JIB_INTEGRATION_TESTING_LOCATION` to a specific registry for testing. (For example, you can run `docker run -d -p 9990:5000 registry:2` to set up a local registry and set the variable to `localhost:9990`.)\n\nYou will also need Docker installed with the daemon running. Note that the\nintegration tests will create local registries on ports 5000 and 6000.\n\nTo run select integration tests, use `--tests=<testPattern>`, see [gradle docs](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/TestFilter.html) for `testPattern` examples.\n\n# Development Tips\n\n## Java version\n\nUse Java 8 or 11 for development. https://sdkman.io/ is a helpful tool to switch between Java versions.\n\n## Configuring Eclipse\n\nAlthough jib is a mix of Gradle and Maven projects, we build everything using one\nunified gradle build. There is special code to include some projects directly as\nsource, but importing your project should be pretty straight forward.\n\n  1. Ensure you have installed the Gradle tooling for Eclipse, called\n     _Buildship_ (available from [the Eclipse\n     Marketplace](https://marketplace.eclipse.org/content/buildship-gradle-integration)).\n  1. **Import the Gradle project:** Buildship does [not yet support\n     Eclipse Smart Import](https://github.com/eclipse/buildship/issues/356).\n     Use _File &rarr; Import &rarr; Gradle &rarr; Existing Gradle Project_\n     and import `jib`.\n\nNote that you will likely need to re-apply these changes whenever\nyou refresh or update these projects.\n\n## Debugging the Jib Maven Plugin (`jib-maven-plugin`)\n\n### Build and use a local snapshot\n\nTo use a local build of the `jib-maven-plugin`:\n\n  1. Build and install `jib-maven-plugin` into your local `~/.m2/repository`\n     with `./gradlew jib-maven-plugin:install`;\n  1. Modify your test project's `pom.xml` to reference the `-SNAPSHOT`\n     version of the `com.google.cloud.tools.jib` plugin.\n\nIf developing from within Eclipse with M2Eclipse (the Maven tooling for Eclipse):\n\n  1. Modify your test project's `pom.xml` to reference the `-SNAPSHOT`\n     version of the `com.google.cloud.tools.jib` plugin.\n  1. Create and launch a _Maven Build_ launch configuration for the\n     test project, and ensure the _Resolve Workspace artifacts_ is checked.\n\n### Attaching a debugger\n\nRun `mvnDebug jib:build` and attach to port 8000.\n\nIf developing with Eclipse and M2Eclipse (the Maven tooling for Eclipse), just launch the _Maven Build_ with _Debug_.\n\n## Debugging the Jib Gradle Plugin (`jib-gradle-plugin`)\n\n### Build and use a local snapshot\n\nTo use a local build of the `jib-gradle-plugin`:\n\n  1. Build and install `jib-gradle-plugin` into your local `~/.m2/repository`\n     with `./gradlew jib-gradle-plugin:install`\n  1. Add a `pluginManagement` block to your test project's `settings.gradle` to enable reading plugins from the local maven repository. It must be the first block in the file before any `include` directives.\n        ```groovy\n        pluginManagement {\n          repositories {\n            mavenLocal()\n            gradlePluginPortal()\n          }\n        }\n        ```\n  1. Modify your test project's `build.gradle` to use the [latest snapshot version](jib-gradle-plugin/gradle.properties)\n        ```groovy\n        plugins {\n          // id 'com.google.cloud.tools.jib' version 'major.minor.patch'\n          id 'com.google.cloud.tools.jib' version 'major.minor.patch-SNAPSHOT'\n        }\n\n        ```\n\n### Attaching a debugger\n\nAttach a debugger to a Gradle instance by running Gradle as follows:\n\n```shell\n./gradlew jib \\\n  --no-daemon \\\n  -Dorg.gradle.jvmargs='-agentlib:jdwp:transport=dt_socket,server=y,address=5005,suspend=y'\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "![stable](https://img.shields.io/badge/stability-stable-brightgreen.svg)\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.cloud.tools/jib-maven-plugin)](https://maven-badges.herokuapp.com/maven-central/com.google.cloud.tools/jib-maven-plugin)\n[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/google/cloud/tools/jib/com.google.cloud.tools.jib.gradle.plugin/maven-metadata.xml.svg?colorB=007ec6&label=gradle)](https://plugins.gradle.org/plugin/com.google.cloud.tools.jib)\n![Build Status](https://storage.googleapis.com/cloud-tools-for-java-kokoro-build-badges/jib-ubuntu-master-orb.svg)\n![Build Status](https://storage.googleapis.com/cloud-tools-for-java-kokoro-build-badges/jib-windows-master-orb.svg)\n![Build Status](https://storage.googleapis.com/cloud-tools-for-java-kokoro-build-badges/jib-macos-master-orb.svg)\n[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)\n[![Gitter version](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/google/jib)\n\n# Jib\n\n<image src=\"https://github.com/GoogleContainerTools/jib/raw/master/logo/jib-build-docker-java-container-image.png\" alt=\"Jib - Containerize your Java applications.\" width=\"650px\" />\n\n| ☑️  Jib User Survey |\n| :----- |\n| What do you like best about Jib? What needs to be improved? Please tell us by taking a [one-minute survey](https://forms.gle/YRFeamGj51xmgnx28). Your responses will help us understand Jib usage and allow us to serve our customers (you!) better. |\n\n## What is Jib?\n\nJib builds optimized Docker and [OCI](https://github.com/opencontainers/image-spec) images for your Java applications without a Docker daemon - and without deep mastery of Docker best-practices. It is available as plugins for [Maven](jib-maven-plugin) and [Gradle](jib-gradle-plugin) and as a Java library.\n\n- [Maven](https://maven.apache.org/): See documentation for [jib-maven-plugin](jib-maven-plugin).\n- [Gradle](https://gradle.org/): See documentation for [jib-gradle-plugin](jib-gradle-plugin).\n- [Jib Core](jib-core): A general-purpose container-building library for Java.\n- [Jib CLI](jib-cli): A command-line interface for building images that uses Jib Core.\n\nJib works well with Google Cloud Build. For details, see [how to use Jib on Google Cloud Build](docs/google-cloud-build.md).\n\nFor more information, check out the [official blog post](https://cloudplatform.googleblog.com/2018/07/introducing-jib-build-java-docker-images-better.html) or watch [this talk](https://www.youtube.com/watch?v=H6gR_Cv4yWI) ([slides](https://speakerdeck.com/coollog/build-containers-faster-with-jib-a-google-image-build-tool-for-java-applications)).\n\n## Goals\n\n* **Fast** - Deploy your changes fast. Jib separates your application into multiple layers, splitting dependencies from classes. Now you don’t have to wait for Docker to rebuild your entire Java application - just deploy the layers that changed.\n\n* **Reproducible** - Rebuilding your container image with the same contents always generates the same image. Never trigger an unnecessary update again.\n\n* **Daemonless** - Reduce your CLI dependencies. Build your Docker image from within Maven or Gradle and push to any registry of your choice. *No more writing Dockerfiles and calling docker build/push.*\n\n## Quickstart\n\n* **Maven** - See the jib-maven-plugin [Quickstart](jib-maven-plugin#quickstart).\n\n* **Gradle** - See the jib-gradle-plugin [Quickstart](jib-gradle-plugin#quickstart).\n\n* **Jib Core** - See the Jib Core [Quickstart](jib-core#adding-jib-core-to-your-build).\n\n* **Jib CLI** - See the Jib CLI [doc](jib-cli).\n\n## Examples\n\nThe [examples](examples) directory includes the following examples (and more).\n   * [helloworld](examples/helloworld)\n   * [Spring Boot](examples/spring-boot)\n   * [Micronaut](examples/micronaut)\n   * [Multi-module project](examples/multi-module)\n   * [Spark Java using Java Agent](examples/java-agent)\n\n## How Jib Works\n\nWhereas traditionally a Java application is built as a single image layer with the application JAR, Jib's build strategy separates the Java application into multiple layers for more granular incremental builds. When you change your code, only your changes are rebuilt, not your entire application. These layers, by default, are layered on top of an [OpenJDK base image](docs/default_base_image.md), but you can also configure a custom base image. For more information, check out the [official blog post](https://cloudplatform.googleblog.com/2018/07/introducing-jib-build-java-docker-images-better.html) or watch [this talk](https://www.youtube.com/watch?v=H6gR_Cv4yWI) ([slides](https://speakerdeck.com/coollog/build-containers-faster-with-jib-a-google-image-build-tool-for-java-applications)).\n\nSee also [rules_docker](https://github.com/bazelbuild/rules_docker) for a similar existing container image build tool for the [Bazel build system](https://github.com/bazelbuild/bazel).\n\n## Need Help?\n\nA lot of questions are already answered!\n\n* [Frequently Asked Questions (FAQ)](docs/faq.md)\n* [Stack Overflow](https://stackoverflow.com/questions/tagged/jib)\n* [GitHub issues](https://stackoverflow.com/questions/tagged/jib)\n\n_For usage questions, please ask them on Stack Overflow._\n\n## Privacy\n\nSee the [Privacy page](docs/privacy.md).\n\n## Get involved with the community\n\nWe welcome contributions! Here's how you can contribute:\n\n* [Browse issues](https://github.com/GoogleContainerTools/jib/issues) or [file an issue](https://github.com/GoogleContainerTools/jib/issues/new)\n* Chat with us on [gitter](https://gitter.im/google/jib)\n* Join the [jib-users mailing list](https://groups.google.com/forum/#!forum/jib-users)\n* Contribute:\n  * *Read the [contributing guide](https://github.com/GoogleContainerTools/jib/blob/master/CONTRIBUTING.md) before starting work on an issue*\n  * Try to fix [good first issues](https://github.com/GoogleContainerTools/jib/labels/good%20first%20issue)\n  * Help out on [issues that need help](https://github.com/GoogleContainerTools/jib/labels/kind%2Fquestion)\n  * Join in on [discussion issues](https://github.com/GoogleContainerTools/jib/labels/discuss)\n<!--  * Read the [style guide] -->\n*Make sure to follow the [Code of Conduct](https://github.com/GoogleContainerTools/jib/blob/master/CODE_OF_CONDUCT.md) when contributing so we can foster an open and welcoming community.*\n\n## Disclaimer\n\nThis is not an officially supported Google product.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nUse this section to tell people about which versions of your project are\ncurrently being supported with security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| jib-maven-plugin v3.x   | :heavy_check_mark: |\n| jib-gradle-plugin v3.x   | :heavy_check_mark:                |\n| jib-core v0.x  | :heavy_check_mark:                |\n| jib-cli v0.x   | :heavy_check_mark:                |\n\n\n## Reporting a Vulnerability\n\nTo report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz).\nWe use g.co/vulnz for our intake, and do coordination and disclosure here on\nGitHub (including using GitHub Security Advisory). The Google Security Team will\nrespond within 5 working days of your report on g.co/vulnz.\n"
  },
  {
    "path": "STYLE_GUIDE.md",
    "content": "# Style guide\n\nThis style guide defines specific coding standards and advice for this Java codebase. The rules here are extensions to the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html).\n\nPlease see the [contributing guide](CONTRIBUTING.md) for general guidance for contributing to this project.\n\n### Automatic formatting\n\nAutomatic formatting should be performed with `./gradlew goJF` or `./mvnw fmt:format`. Formatting all projects can be done with `./build.sh format`.\n\n### Class member order\n\n*Extends [3.4.2](https://google.github.io/styleguide/javaguide.html#s3.4.2-ordering-class-contents)*\n\nClass members should be in the following order, in decreasing priority:\n\n1. Static before non-static\n1. Nested classes/interfaces before fields before constructors before methods\n1. Public before private\n1. Final before non-final\n\n### Public APIs\n\nUser-facing methods (such as those in Jib Core) should not have types in their signature that are not standard JDK classes. For example, a parameter should take type `List` rather than Guava's `ImmutableList`.\n\nJib Core's formal API should not expose internal Jib types. In other words, public classes in the `com.google.cloud.tools.jib.api` package should not contain any public methods that have internal types (Jib classes outside of the `api` package) in the method signature. This includes return types, parameters, thrown types, and javadoc links on public methods.\n\n### Package hierarchy\n\nPackages should depend on each other without cycles.\n\nThe following is a list of current `jib-core` packages (under `com.google.cloud.tools.jib`) and their immediate dependencies. These can be amended as code changes, but there should not be cyclical dependencies.\n\n- `api`\n- `async`\n- `blob` - `filesystem`, `hash`, `image` (cycle - should fix)\n- `builder` - `async`, `blob`, `builder`, `cache` `configuration`, `docker`, `event`, `filesystem`, `global`, `http`, `image`, `json`, `registry`\n- `cache` - `blob`, `filesystem`, `hash`, `image`, `json`\n- `configuration` - `cache`, `filesystem`, `event`, `image`, `registry`\n- `docker` - `blob`, `cache`, `image`, `json`, `tar`\n- `event`\n- `filesystem`\n- `frontend` - `configuration`, `event`, `filesystem`, `image`, `registry`\n- `global`\n- `hash` - `blob`, `image`\n- `http` - `blob`\n- `image` - `blob`, `configuration` (cycle - should fix - `ImageToJsonTranslator`), `filesystem`, `json`, `tar`\n- `json` - `blob`\n- `registry` - `blob`, `builder` (cycle - should fix - `RegistryClient`), `configuration` (cycle - should fix - `DockerConfigCredentialRetriever`), `event`, `global`, `http`, `image`, `json`\n- `tar` - `blob`\n"
  },
  {
    "path": "build.gradle",
    "content": "// define all versioned plugins here and apply in subprojects as necessary without version\nplugins {\n  id 'com.github.sherter.google-java-format' version '0.9' apply false\n  id 'net.ltgt.errorprone' version '3.1.0' apply false\n  id 'net.researchgate.release' version '2.8.1' apply false\n  id 'com.gradle.plugin-publish' version '1.2.0' apply false\n  id 'io.freefair.maven-plugin' version '5.3.3.3' apply false\n\n  // apply so that we can collect quality metrics at the root project level\n  id 'org.sonarqube' version '4.0.0.2929'\n}\n\n/* PROJECT DEPENDENCY VERSIONS */\n// define all common versioned dependencies here\nproject.ext.dependencyStrings = [\n  // For Google libraries, check the following boms for best compatibility.\n  // - https://github.com/googleapis/java-shared-dependencies\n  // - https://github.com/googleapis/java-cloud-bom\n  GOOGLE_HTTP_CLIENT: 'com.google.http-client:google-http-client:1.42.2',\n  GOOGLE_HTTP_CLIENT_APACHE_V2: 'com.google.http-client:google-http-client-apache-v2:1.42.2',\n  GOOGLE_AUTH_LIBRARY_OAUTH2_HTTP: 'com.google.auth:google-auth-library-oauth2-http:1.10.0',\n  GUAVA: 'com.google.guava:guava:32.1.2-jre',\n  JSR305: 'com.google.code.findbugs:jsr305:3.0.2', // transitively pulled in by GUAVA\n\n  // for Build Plan and Jib Plugins Extension API\n  BUILD_PLAN: 'com.google.cloud.tools:jib-build-plan:0.4.0',\n  EXTENSION_COMMON: 'com.google.cloud.tools:jib-plugins-extension-common:0.2.0',\n  GRADLE_EXTENSION: 'com.google.cloud.tools:jib-gradle-plugin-extension-api:0.4.0',\n  MAVEN_EXTENSION: 'com.google.cloud.tools:jib-maven-plugin-extension-api:0.4.0',\n\n  COMMONS_COMPRESS: 'org.apache.commons:commons-compress:1.26.0',\n  ZSTD_JNI: 'com.github.luben:zstd-jni:1.5.5-5',\n  COMMONS_TEXT: 'org.apache.commons:commons-text:1.10.0',\n  JACKSON_BOM: 'com.fasterxml.jackson:jackson-bom:2.15.2',\n  JACKSON_DATABIND: 'com.fasterxml.jackson.core:jackson-databind',\n  JACKSON_DATAFORMAT_YAML: 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml',\n  JACKSON_DATATYPE_JSR310: 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310',\n  ASM: 'org.ow2.asm:asm:9.9',\n  PICOCLI: 'info.picocli:picocli:4.7.4',\n\n  MAVEN_API: 'org.apache.maven:maven-plugin-api:3.9.3',\n  MAVEN_CORE: 'org.apache.maven:maven-core:3.9.3',\n  MAVEN_COMPAT: 'org.apache.maven:maven-compat:3.9.6',\n  MAVEN_PLUGIN_ANNOTATIONS: 'org.apache.maven.plugin-tools:maven-plugin-annotations:3.9.0',\n\n  //test\n  TRUTH: 'com.google.truth:truth:1.1.5',\n  TRUTH8: 'com.google.truth.extensions:truth-java8-extension:1.1.5', // should match TRUTH version\n  JUNIT: 'junit:junit:4.13.2',\n  JUNIT_PARAMS: 'pl.pragmatists:JUnitParams:1.1.1',\n  MAVEN_TESTING_HARNESS: 'org.apache.maven.plugin-testing:maven-plugin-testing-harness:3.3.0',\n  MAVEN_VERIFIER: 'org.apache.maven.shared:maven-verifier:1.8.0',\n  MOCKITO_CORE: 'org.mockito:mockito-core:4.11.0',\n  SISU_PLEXUS: 'org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5',\n  SLF4J_API: 'org.slf4j:slf4j-api:2.0.7',\n  SLF4J_SIMPLE: 'org.slf4j:slf4j-simple:2.0.9',\n  SYSTEM_RULES:  'com.github.stefanbirkner:system-rules:1.19.0',\n  JBCRYPT: 'org.mindrot:jbcrypt:0.4',\n]\n\nimport net.ltgt.gradle.errorprone.CheckSeverity\n\n// `java-library` must be applied before `java`.\n// java-gradle-plugin (in jib-gradle-plugin) auto applies java-library, so ensure that happens first\n['jib-core', 'jib-gradle-plugin', 'jib-gradle-plugin-extension-api', 'jib-maven-plugin-extension-api'].each { projectName ->\n  project(projectName).apply plugin: 'java-library'\n}\n\nsubprojects {\n  group 'com.google.cloud.tools'\n\n  repositories {\n    mavenCentral()\n  }\n\n  apply plugin: 'java'\n  apply plugin: 'checkstyle'\n  apply plugin: 'com.github.sherter.google-java-format'\n  apply plugin: 'net.ltgt.errorprone'\n  apply plugin: 'jacoco'\n\n  // Guava update breaks unit tests. Workaround mentioned in https://github.com/google/guava/issues/6612#issuecomment-1614992368.\n  sourceSets.all {\n    configurations.getByName(runtimeClasspathConfigurationName) {\n      attributes.attribute(Attribute.of(\"org.gradle.jvm.environment\", String), \"standard-jvm\")\n    }\n    configurations.getByName(compileClasspathConfigurationName) {\n      attributes.attribute(Attribute.of(\"org.gradle.jvm.environment\", String), \"standard-jvm\")\n    }\n  }\n\n  sourceCompatibility = JavaVersion.VERSION_1_8\n  targetCompatibility = JavaVersion.VERSION_1_8\n  compileJava.options.encoding = 'UTF-8'\n  compileJava.options.compilerArgs += [ '-Xlint:deprecation' ]\n  compileTestJava.options.compilerArgs += [ '-Xlint:deprecation' ]\n\n  // Use this to ensure we correctly override transitive dependencies\n  // TODO: There might be a plugin that does this\n  task ensureTransitiveDependencyOverrides {\n    def dependenciesList = [dependencyStrings.GOOGLE_HTTP_CLIENT, dependencyStrings.GOOGLE_HTTP_CLIENT_APACHE_V2]\n    def rules = dependenciesList.collectEntries{[/*name*/ it.split(':')[1], /*version*/ it.split(':')[2]]}\n    doLast {\n      configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact ->\n        def dependency = artifact.moduleVersion.id\n        if (rules[dependency.name] && rules[dependency.name] != dependency.version) {\n          throw new GradleException(\n              dependency.name + ' version error in ' + project\n              + ', expected:' + rules[dependency.name]\n              + ', found:' + dependency.version);\n        }\n      }\n    }\n  }\n  compileJava.dependsOn ensureTransitiveDependencyOverrides\n  /* PROJECT DEPENDENCY VERSIONS */\n\n  /* ERROR PRONE */\n  dependencies {\n    // NullAway errorprone plugin\n    annotationProcessor 'com.uber.nullaway:nullaway:0.10.7'\n    errorprone 'com.google.errorprone:error_prone_core:2.10.0'\n    // Using github.com/google/error-prone-javac is required when running on\n    // JDK 8. Remove when migrating to JDK 11.\n    if (System.getProperty('java.version').startsWith('1.8.')) {\n      errorproneJavac('com.google.errorprone:javac:9+181-r4173-1')\n    }\n  }\n\n  // Adds NullAway errorprone checks.\n  tasks.withType(JavaCompile) {\n    if (!name.toLowerCase().contains('test')) {\n      options.errorprone {\n        check('NullAway', CheckSeverity.ERROR)\n        option('NullAway:ExcludedFieldAnnotations', 'org.apache.maven.plugins.annotations.Component')\n        option('NullAway:AnnotatedPackages', 'com.google.cloud.tools')\n      }\n    }\n  }\n  /* ERROR PRONE */\n\n  /* GOOGLE JAVA FORMAT */\n  googleJavaFormat {\n    toolVersion = '1.7'\n  }\n  check.dependsOn verifyGoogleJavaFormat\n  /* GOOGLE JAVA FORMAT */\n\n  /* CHECKSTYLE */\n  checkstyle {\n    toolVersion = '8.29'\n\n    // use google checks from the jar\n    def googleChecks = resources.text.fromArchiveEntry(configurations.checkstyle[0], 'google_checks.xml').asString()\n\n    // set the location of the suppressions file referenced in google_checks.xml\n    configProperties['org.checkstyle.google.suppressionfilter.config'] = getConfigDirectory().file('checkstyle-suppressions.xml').get().toString()\n\n    // add in copyright header check on only java files (replace the last </module> in file)\n    def copyrightChecks = '''\n        <module name=\"RegexpHeader\">\n            <property name=\"headerFile\" value=\"${config_loc}/copyright-java.header\"/>\n            <property name=\"fileExtensions\" value=\"java\"/>\n            <property name=\"id\" value=\"header\"/>\n        </module>\n    </module>\n    '''\n    googleChecks = googleChecks.substring(0, googleChecks.lastIndexOf('</module>')) + copyrightChecks\n\n    // this is the actual checkstyle config\n    config = resources.text.fromString(googleChecks)\n\n    maxErrors = 0\n    maxWarnings = 0\n  }\n  /* CHECKSTYLE */\n\n  /* TEST CONFIG */\n  tasks.withType(Test).configureEach {\n    reports.html.outputLocation = file(\"${reporting.baseDir}/${name}\")\n  }\n\n  test {\n    testLogging {\n      showStandardStreams = true\n      exceptionFormat = 'full'\n    }\n  }\n  // jar to export tests classes for import in other project by doing:\n  // testCompile project(path:':project-name', configuration:'tests')\n  task testJar(type: Jar) {\n    from sourceSets.test.output.classesDirs\n    archiveClassifier = 'tests'\n  }\n  // to import resources do: sourceSets.test.resources.srcDirs project(':project-name').sourceSets.test.resources\n\n  configurations {\n    tests\n  }\n\n  artifacts {\n    tests testJar\n  }\n  /* TEST CONFIG */\n\n  /* INTEGRATION TESTS */\n  sourceSets {\n    integrationTest {\n      java.srcDir file('src/integration-test/java')\n      resources.srcDir file('src/integration-test/resources')\n      compileClasspath += sourceSets.main.output + sourceSets.test.output\n      runtimeClasspath += sourceSets.main.output + sourceSets.test.output\n    }\n  }\n\n  configurations {\n    integrationTestImplementation.extendsFrom testImplementation\n    integrationTestImplementation.setCanBeResolved(true)\n    integrationTestRuntime.extendsFrom testRuntime\n  }\n\n  // Integration tests must be run explicitly\n  task integrationTest(type: Test) {\n    testClassesDirs = sourceSets.integrationTest.output.classesDirs\n    classpath = sourceSets.integrationTest.runtimeClasspath\n    systemProperty '_JIB_DISABLE_USER_AGENT', true\n  }\n\n\n  task integrationTestJar(type: Jar) {\n    from sourceSets.integrationTest.output.classesDirs\n    archiveClassifier = 'integration-tests'\n  }\n\n  configurations {\n    integrationTests\n  }\n\n  artifacts {\n    integrationTests integrationTestJar\n  }\n\n  integrationTest {\n    testLogging {\n      showStandardStreams = true\n      exceptionFormat = 'full'\n    }\n  }\n  /* INTEGRATION TESTS */\n\n  /* JAVADOC ENFORCEMENT */\n  // Fail build on javadoc warnings\n  tasks.withType(Javadoc) {\n    options.addBooleanOption('Xwerror', true)\n  }\n  assemble.dependsOn javadoc\n  /* JAVADOC ENFORCEMENT */\n\n  /* JAR */\n  jar {\n    manifest {\n      attributes 'Implementation-Title': project.name,\n                 'Implementation-Version': archiveVersion,\n                 'Built-By': System.getProperty('user.name'),\n                 'Built-Date': new Date(),\n                 'Built-JDK': System.getProperty('java.version'),\n                 'Built-Gradle': gradle.gradleVersion\n    }\n  }\n  normalization {\n    runtimeClasspath {\n      metaInf {\n        ignoreAttribute(\"Built-By\")\n        ignoreAttribute(\"Built-Date\")\n      }\n    }\n  }\n  /* JAR */\n\n  /* MAVEN CENTRAL RELEASES */\n  // for projects that release to maven central\n  project.ext.configureMavenRelease = {\n    apply plugin: 'maven-publish'\n    task sourceJar(type: Jar) {\n      from sourceSets.main.allJava\n      archiveClassifier = 'sources'\n    }\n\n    task javadocJar(type: Jar, dependsOn: javadoc) {\n      from javadoc.destinationDir\n      archiveClassifier = 'javadoc'\n    }\n\n    publishing {\n      publications {\n        mavenJava(MavenPublication) {\n          pom {\n            // to be filled by subproject after calling configure configureMavenRelease\n            // name = ''\n            // description = ''\n\n            url = 'https://github.com/GoogleContainerTools/jib'\n            inceptionYear = '2018'\n\n            licenses {\n              license {\n                name = 'The Apache License, Version 2.0'\n                url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                distribution = 'repo'\n              }\n            }\n            developers {\n              developer {\n                id = 'chanseokoh'\n                name = 'Chanseok Oh'\n                email = 'chanseok@google.com'\n              }\n              developer {\n                id = 'loosebazooka'\n                name = 'Appu Goundan'\n                email = 'appu@google.com'\n              }\n              developer {\n                id = 'TadCordle'\n                name = 'Tad Cordle'\n                email = 'tcordle@google.com'\n              }\n              developer {\n                id = 'briandealwis'\n                name = 'Brian de Alwis'\n                email = 'bdealwis@google.com'\n              }\n              developer {\n                id = 'coollog'\n                name = 'Qingyang Chen'\n              }\n            }\n            scm {\n              url = 'https://github.com/GoogleContainerTools/jib'\n              connection = 'scm:https://github.com/GoogleContainerTools/jib.git'\n              developerConnection = 'scm:git://github.com/GoogleContainerTools/jib.git'\n            }\n          }\n        }\n      }\n    }\n    generatePomFileForMavenJavaPublication {\n      destination = file(\"${project.buildDir}/pom/${project.name}-${project.version}.pom\")\n    }\n    // define a special install task that handles installing locally for manual testing\n    task install {\n      dependsOn publishToMavenLocal\n    }\n\n    // For kokoro sign and release to maven central\n    task prepareRelease(type: Copy) {\n      from jar\n      from sourceJar\n      from javadocJar\n      from generatePomFileForMavenJavaPublication\n      into \"${project.buildDir}/release-artifacts\"\n      dependsOn build\n      dependsOn cleanPrepareRelease\n    }\n  }\n  /* MAVEN CENTRAL RELEASE */\n\n  /* INCLUDED PROJECT DEPENDENCY HELPER */\n  // to keep track of all source projects\n  project.ext.sourceProjects = []\n  // sourceProject(Project) accepts a project and adds it as a dependency in a special manner:\n  // 1. force evaluation of the project first\n  // 2. add the project classes as \"compileOnly\" and make it available to tests in \"testImplementation\"\n  // 3. add the project's dependencies as \"implementation\"\n  // 4. remove any transitive reference of any sourceProject depenency that may have appeared\n  // 5. add the project's classes to the final jar\n  // Other nice effects (vs shadowJar)\n  // 1. Generated poms will be correct\n  // 2. Configuration is isolated to this single \"sourceProject\" call\n  // 3. These configurations are compliant with IDEs\n  project.ext.sourceProject = { Project dependencyProject ->\n    // make sure those projects are evaluated first so we know their dependencies\n    project.evaluationDependsOn dependencyProject.path\n    // add the sourceProjecect dependency\n    def dependencyProjectClasses = dependencyProject.sourceSets.main.output\n    dependencies {\n      // add the dependencyProject classes as compileOnly, make it available to tests\n      compileOnly(dependencyProject) { transitive = false }\n      testImplementation dependencyProjectClasses\n      // add dependencyProject's dependencies as implementation dependencies\n      implementation dependencyProject.configurations.implementation.dependencies\n      if (dependencyProject.configurations.hasProperty('api')) {\n        implementation dependencyProject.configurations.api.dependencies\n      }\n    }\n    // keep track of all dependencyProjects for removal\n    sourceProjects += dependencyProject\n    // if we find any project dependencies that were brought in transitively, go remove them\n    project.configurations.implementation.dependencies.removeAll { d ->\n      return d instanceof ProjectDependency && sourceProjects.contains(d.dependencyProject)\n    }\n    // adds dependencyProject's classes to jar (fat jar-esque)\n    jar {\n      from dependencyProjectClasses\n    }\n    // also configure the java-gradle-plugin if necessary\n    if (project.hasProperty('gradlePlugin')) {\n      project.tasks.pluginUnderTestMetadata.pluginClasspath.from dependencyProjectClasses\n    }\n  }\n\n  // ensure no dependencies in the implementation group are project dependencies\n  project.ext.ensureNoProjectDependencies = {\n    project.afterEvaluate {\n      project.configurations.implementation.dependencies.each { dependency ->\n        if (dependency instanceof ProjectDependency) {\n          throw new GradleException('disallowed project dependency:' + dependency + ', in project:' + project);\n        }\n      }\n    }\n  }\n\n  /* TEST COVERAGE */\n  jacocoTestReport {\n    reports {\n      xml.required = true\n      html.required = false\n    }\n  }\n  /* TEST COVERAGE */\n\n  /* INCLUDED PROJECT DEPENDENCY HELPER */\n\n  /* LOCAL DEVELOPMENT HELPER TASKS */\n  tasks.register('dev') {\n    classes.dependsOn tasks.googleJavaFormat\n    dependsOn check\n    dependsOn javadoc\n  }\n\n  tasks.register('devFull') {\n    dependsOn dev\n    dependsOn integrationTest\n  }\n  /* LOCAL DEVELOPMENT HELPER TASKS */\n}\n\n/* SONARQUBE */\nsonarqube {\n  properties {\n    property 'sonar.projectName', 'jib'\n    property 'sonar.projectKey', 'GoogleContainerTools_jib'\n    property 'sonar.host.url', 'https://sonarcloud.io'\n    property 'sonar.organization', 'googlecontainertools-1'\n  }\n}\n/* SONARQUBE */\n"
  },
  {
    "path": "config/checkstyle/checkstyle-suppressions.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE suppressions PUBLIC\n  \"-//Puppy Crawl//DTD Suppressions 1.1//EN\"\n  \"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd\">\n<suppressions>\n  <!-- clash with google java format -->\n  <suppress files=\".*\\.java\" checks=\"SingleLineJavadoc\"/>\n  <suppress files=\".*\\.java\" checks=\"Indentation\"/>\n  <suppress files=\".*\\.java\" checks=\"RightCurly\"/>\n  <suppress files=\".*\\.java\" checks=\"LineLength\"/>\n\n  <!-- class with jib repo style: although maybe these should be eventually fixed -->\n  <suppress files=\".*\\.java\" checks=\"OverloadMethodsDeclarationOrder\"/>\n\n  <!-- temporary suppressions as we work through them -->\n  <suppress files=\".*\\.java\" checks=\"VariableDeclarationUsageDistance\"/>\n\n  <!-- leniency for json templates -->\n  <suppress files=\".*[\\\\/]jib-core[\\\\/].*Template\\.java\" checks=\"MemberName\"/>\n\n  <!-- leniency for gradle extension configuration objects -->\n  <suppress files=\".*[\\\\/]jib-gradle-plugin[\\\\/].*Parameters\\.java\" checks=\"MissingJavadocMethod\"/>\n\n  <!-- leniency for helpful suggestions -->\n  <suppress files=\".*[\\\\/]HelpfulSuggestions\\.java\" checks=\"MissingJavadocMethod\"/>\n\n  <!-- leniency for test files -->\n  <suppress files=\".*[\\\\/]src[\\\\/]test[\\\\/].*\\.java\" checks=\"MissingJavadocMethod\"/>\n  <suppress files=\".*[\\\\/]src[\\\\/]integration-test[\\\\/].*\\.java\" checks=\"MissingJavadocMethod\"/>\n  <suppress files=\".*[\\\\/]src[\\\\/]integration-test[\\\\/].*\\.java\" checks=\"VariableDeclarationUsageDistance\"/>\n\n</suppressions>\n"
  },
  {
    "path": "config/checkstyle/copyright-java.header",
    "content": "^/\\*$\n^ \\* Copyright 20(17|18|19|20|21|22|23|24) Google LLC\\.$\n^ \\*$\n^ \\* Licensed under the Apache License, Version 2\\.0 \\(the \"License\"\\); you may not$\n^ \\* use this file except in compliance with the License\\. You may obtain a copy of$\n^ \\* the License at\n^ \\*$\n^ \\*      http://www\\.apache\\.org/licenses/LICENSE-2\\.0$\n^ \\*$\n^ \\* Unless required by applicable law or agreed to in writing, software$\n^ \\* distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT$\n^ \\* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\\. See the$\n^ \\* License for the specific language governing permissions and limitations under$\n^ \\* the License\\.$\n^ \\*/$\n"
  },
  {
    "path": "docs/configure-gcp-credentials.md",
    "content": "# Configuring Credentials for [Google Container Registry (GCR)](https://cloud.google.com/container-registry/)\n\nThere are a few ways of supplying Jib with the credentials to push and pull images from your private GCR registry.\n\n## Using the Docker credential helper\n\nThe easiest way is to install the [docker-credential-gcr](https://github.com/GoogleCloudPlatform/docker-credential-gcr).\n\n### Installation\n\nIf you have [`gcloud` (Cloud SDK)](https://cloud.google.com/sdk/gcloud/) installed, you can run:\n\n```shell\ngcloud components install docker-credential-gcr\n```\n\nAlternatively, if you have `go get` installed, you can run:\n\n```shell\ngo get -u github.com/GoogleCloudPlatform/docker-credential-gcr\n```\n\nAlternatively, you can download `docker-credential-gcr` from its [Github Releases](https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases).\n\n### Enable the Container Registry API\n\nIf you have not already done so, make sure you [enable the Container Registry API](https://console.cloud.google.com/flows/enableapi?apiid=containerregistry.googleapis.com&redirect=https://github.com/GoogleContainerRegistry/jib) for the Google Cloud Platform account you wish to use.\n\n### Log in\n\nLog in to the account you with to use with:\n\n```shell\ndocker-credential-gcr gcr-login\n```\n\nThis stores the credentials in `docker-credential-gcr`'s private credential store.\n\nNow, you can use Jib to pull and push from images in the form `gcr.io/your-gcp-project/your-image-name`.\n"
  },
  {
    "path": "docs/default_base_image.md",
    "content": "# Default Base Images in Jib\n\n## Jib Build Plugins 3+\n\nStarting from version 3.2, the default base image is the official [`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin) image on Docker Hub. Note that Eclipse Temurin by Adoptium is the [successor of AdoptOpenJDK](https://blog.adoptopenjdk.net/2021/08/goodbye-adoptopenjdk-hello-adoptium/). (For versions 3.0 and 3.1, the default is the official [`adoptopenjdk`](https://hub.docker.com/_/adoptopenjdk) image.)\n\nFor WAR projects, the default is the official [`jetty`](https://hub.docker.com/_/jetty) image on Docker Hub.\n\nNote that Jib's default choice for Temurin, AdoptOpenJDK, and Jetty does not imply any endorsement to them. In fact, for strong reproducibility (which also results in better performance and efficiency), we always recommend configuring [`jib.from.image`](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#from-closure) (Gradle) or [`<from><image>`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#from-object) (Maven) to pin to a specific base image using a digest (or at least a tag). And while doing so, you should do your due diligence to figure out which base image will work best for you.\n\n### Docker Hub Download Rate Limit\n\nDocker Hub enforces download rate limit. Because Jib pulls base images from Docker Hub, you can occasionally lead to the following error:\n```\n    > com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException: 429 Too Many Requests\n      GET https://registry-1.docker.io/v2/library/adoptopenjdk/manifests/sha256:9db4a57b38b6d928761ec9c5a250677d306095df0f6a6bdd8936628e033b9f1a\n      {\n        \"errors\": [\n          {\n            \"code\": \"TOOMANYREQUESTS\",\n            \"message\": \"You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit\"\n          }\n        ]\n      }\n```\nNote that, even after Jib fully cached a base image, Jib still connects to Docker Hub to check the image every time it runs (unless you pin to a specific base image using a SHA digest). This is to check if the cached base image is up-to-date.\n\nSome options:\n* Configure a registry mirror.\n* Prevent Jib from accessing Docker Hub (after Jib cached a base image locally).\n   - Pin to a specific base image using a SHA digest (for example, `jib.from.image='eclipse-temurin:11-jre@sha256:...'`).\n   - Do offline building.\n   - Read a base from a local Docker daemon.\n   - Set up a local registry, store a base image, and read it from the local registry.\n* Retry with increasing backoffs.\n\nSee this [FAQ](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#i-am-hitting-docker-hub-rate-limits-how-can-i-configure-registry-mirrors) for more details about the options.\n\n### Migration from Pre-3.0\n\nPrior to 3.0, Jib Maven and Gradle plugins built images based on Distroless Java when the user didn't make an explicit choice. The Jib team carefully assessed all the surrounding factors and ultimately decided to switch from Distroless for the best interest of the Jib users. That is, to meet one of the core missions of Jib to build a secure and optimized image.\n\nIf you are already setting a specific base image, there will be absolutely no difference when you upgrade to 3.0. It will continue to use the base image you specified. Even if you are not specifying a base image, most likely upgrading to 3.0 will keep working fine, because it's just about getting a JRE from a different image. (But if you are not setting a base image, as we explained in the previous section, we highly recommend configuring it.)\n\nFor some reason if you have to keep the exact same behavior when using 3.0, you can always specify the Distroless Java as a base image.\n\n* non-WAR projects\n   ```groovy\n   jib {\n     from.image = 'gcr.io/distroless/java:11' // or \":8\"\n     ...\n   }\n   ```\n   ```xml\n     <configuration>\n       <from><image>gcr.io/distroless/java:11</image></from> <!-- or \":8\" -->\n     </configuration>\n   ```\n   However, even when you decide to keep using Distroless, at least we strongly recommend `gcr.io/distroless/java-debian10:11`, because, as of Apr 2021, `gcr.io/distroless/java:{8,11}` is based on Debian 9 that reached end-of-life. (Note `gcr.io/distroless/java-debian10` doesn't have `:8`.)\n\n* WAR projects\n   ```gradle\n   jib {\n     from.image = 'gcr.io/distroless/java/jetty:java11' // or \":java8\"\n     container {\n       entrypoint = 'INHERIT'\n       appRoot = '/jetty/webapps/ROOT'\n     }\n     ...\n   }\n   ```\n   ```xml\n     <configuration>\n       <from><image>gcr.io/distroless/java/jetty:java11</image></from> <!-- or \":java8\" -->\n       <container>\n         <entrypoint>INHERIT</entrypoint>\n         <appRoot>/jetty/webapps/ROOT</appRoot>\n       </container>\n     </configuration>\n   ```\n\n## Jib CLI\n\nFor the JAR mode, Jib CLI versions prior to 0.8 have always used AdoptOpenJDK. Starting with 0.8, the tool uses [`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin). The WAR mode uses `jetty`.\n"
  },
  {
    "path": "docs/faq.md",
    "content": "## Frequently Asked Questions (FAQ)\n\nIf a question you have is not answered below, please [submit an issue](/../../issues/new).\n\n| ☑️  Jib User Survey |\n| :----- |\n| What do you like best about Jib? What needs to be improved? Please tell us by taking a [one-minute survey](https://forms.gle/YRFeamGj51xmgnx28). Your responses will help us understand Jib usage and allow us to serve our customers (you!) better. |\n\n[But, I'm not a Java developer.](#but-im-not-a-java-developer)\\\n[My build process doesn't let me integrate with the Jib Maven or Gradle plugin](#my-build-process-doesnt-let-me-integrate-with-the-jib-maven-or-gradle-plugin)\\\n[How do I run the image I built?](#how-do-i-run-the-image-i-built)\\\n[Where is bash?](#where-is-bash)\\\n[What image format does Jib use?](#what-image-format-does-jib-use)\\\n[Why is my image created 48+ years ago?](#why-is-my-image-created-48-years-ago)\\\n[Where is the application in the container filesystem?](#where-is-the-application-in-the-container-filesystem)\\\n[How are Jib applications layered?](#how-are-jib-applications-layered)\\\n[Can I learn more about container images?](#can-i-learn-more-about-container-images)\\\n[Which base image (JDK) does Jib use?](#which-base-image-jdk-does-jib-use)\n\n**How-Tos**\\\n[How do I set parameters for my image at runtime?](#how-do-i-set-parameters-for-my-image-at-runtime)\\\n[Can I define a custom entrypoint?](#can-i-define-a-custom-entrypoint-at-runtime)\\\n[I want to containerize a JAR.](#i-want-to-containerize-a-jar)\\\n[I need to RUN commands like `apt-get`.](#i-need-to-run-commands-like-apt-get)\\\n[Can I ADD a custom directory to the image?](#can-i-add-a-custom-directory-to-the-image)\\\n[I need to add files generated during the build process to a custom directory on the image.](#i-need-to-add-files-generated-during-the-build-process-to-a-custom-directory-on-the-image)\\\n[Can I build to a local Docker daemon?](#can-i-build-to-a-local-docker-daemon)\\\n[How do I enable debugging?](#how-do-i-enable-debugging)\\\n[What would a Dockerfile for a Jib-built image look like?](#what-would-a-dockerfile-for-a-jib-built-image-look-like)\\\n[How can I inspect the image Jib built?](#how-can-i-inspect-the-image-jib-built)\\\n[I would like to run my application with a javaagent.](#i-would-like-to-run-my-application-with-a-javaagent)\\\n[How can I tag my image with a timestamp?](#how-can-i-tag-my-image-with-a-timestamp)\\\n[How do I specify a platform in the manifest list (or OCI index) of a base image?](#how-do-i-specify-a-platform-in-the-manifest-list-or-oci-index-of-a-base-image)\\\n[I want to exclude files from layers, have more fine-grained control over layers, change file ownership, etc.](#i-want-to-exclude-files-from-layers-have-more-fine-grained-control-over-layers-change-file-ownership-etc)\\\n[Jib build plugins don't have the feature that I need.](#jib-build-plugins-dont-have-the-feature-that-i-need)\\\n[I am hitting Docker Hub rate limits. How can I configure registry mirrors?](#i-am-hitting-docker-hub-rate-limits-how-can-i-configure-registry-mirrors)\\\n[Where is the global Jib configuration file and how I can configure it?](#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it)\n\n**Build Problems**\\\n[How can I diagnose problems pulling or pushing from remote registries?](#how-can-i-diagnose-problems-pulling-or-pushing-from-remote-registries)\\\n[What should I do when the registry responds with Forbidden or DENIED?](#what-should-i-do-when-the-registry-responds-with-forbidden-or-denied)\\\n[What should I do when the registry responds with UNAUTHORIZED?](#what-should-i-do-when-the-registry-responds-with-unauthorized)\\\n[How do I configure a proxy?](#how-do-i-configure-a-proxy)\\\n[How can I examine network traffic?](#how-can-i-examine-network-traffic)\\\n[How do I view debug logs for Jib?](#how-do-i-view-debug-logs-for-jib)\\\n[I am seeing `Method Not Found` or `Class Not Found` errors when building.](#i-am-seeing-method-not-found-or-class-not-found-errors-when-building)\\\n[I am seeing `Unsupported class file major version` when building.](#i-am-seeing-unsupported-class-file-major-version-when-building)\\\n[I am seeing `NoClassDefFoundError: com/github/luben/zstd/ZstdOutputStream` when building.](#i-am-seeing-noclassdeffounderror-comgithublubenzstdzstdoutputstream-when-building)\n\n**Launch Problems**\\\n[I am seeing `ImagePullBackoff` on my pods.](#i-am-seeing-imagepullbackoff-on-my-pods-in-minikube)\\\n[Why won't my container start?](#why-wont-my-container-start)\n\n**Jib CLI**\\\n[How does the `jar` command support Standard JARs?](#how-does-the-jar-command-support-standard-jars)\\\n[How does the `jar` command support Spring Boot JARs?](#how-does-the-jar-command-support-spring-boot-jars)\\\n[How does the `war` command work?](#how-does-the-war-command-work)\n\n---\n\n### But, I'm not a Java developer.\n\nCheck out [Jib CLI](https://github.com/GoogleContainerTools/jib/tree/master/jib-cli), a general-purpose command-line tool for building containers images from filesystem content.\n\nAlso see [rules_docker](https://github.com/bazelbuild/rules_docker) for a similar existing container image build tool for the [Bazel build system](https://github.com/bazelbuild/bazel). The tool can build images for languages such as Python, NodeJS, Java, Scala, Groovy, C, Go, Rust, and D.\n\n### My build process doesn't let me integrate with the Jib Maven or Gradle plugin\n\nThe [Jib CLI](https://github.com/GoogleContainerTools/jib/tree/master/jib-cli) can be useful for users with complex build workflows that make it hard to integrate the Jib Maven or Gradle plugin. It is a standalone application that is powered by [Jib Core](https://github.com/GoogleContainerTools/jib/tree/master/jib-core) and offers two commands:\n\n* [Build](https://github.com/GoogleContainerTools/jib/tree/master/jib-cli#build-command): Builds images from the filesystem content.\n \n* [Jar](https://github.com/GoogleContainerTools/jib/tree/master/jib-cli#jar-command): Examines your JAR and builds an image with optimized layers or containerizes the JAR as-is.\n\nCheck out the [Jib CLI section](#jib-cli) of the FAQ for more information.\n\n### How do I run the image I built?\n\nIf you built your image directly to the Docker daemon using `jib:dockerBuild` (Maven) or `jibDockerBuild` (Gradle), you simply need to use `docker run <image name>`.\n\nIf you built your image to a registry using `jib:build` (Maven) or `jib` (Gradle), you will need to pull the image using `docker pull <image name>` before using `docker run`.\n\nTo run your image on Kubernetes, you can use kubectl:\n\n```shell\nkubectl run jib-deployment --image=<image name>\n```\n\nFor more information, see [steps 4-6 of the Kubernetes Engine deployment tutorial](https://cloud.google.com/kubernetes-engine/docs/tutorials/hello-app#step_4_create_a_container_cluster).\n\n### Where is bash?\n\nBy default, Jib Maven and Gradle plugin versions prior to 3.0 used [`distroless/java`](https://github.com/GoogleContainerTools/distroless/tree/master/java) as the base image, which did not have a shell program (such as `sh`, `bash`, or `dash`). However, recent Jib tools use [default base images](default_base_image.md) that come with shell programs: Adoptium Eclipse Temurin (formerly AdoptOpenJDK) and Jetty for WAR projects.\n\nNote that you can always set a different base image. Jib's default choice for Temurin or AdoptOpenJDK does not imply any endorsement to it; you should do your due diligence to choose the right image that works best for you. Also note that the default base image is unpinned (the tag can point to different images over time), so we recommend configuring a base image with a SHA digest for strong reproducibility.\n\n* Configuring a base image in Maven\n   ```xml\n   <configuration>\n     <from>\n       <image>openjdk:11-jre-slim@sha256:...</image>\n     </from>\n   </configuration>\n   ```\n\n* Configuring a base image in Gradle\n   ```groovy\n   jib.from.image = 'openjdk:11-jre-slim@sha256:...'\n   ```\n\n* Configuring a base image in Jib CLI\n   ```\n   $ jib jar --from openjdk:11-jre-slim@sha256:... --target ... app.jar\n   ```\n\n### What image format does Jib use?\n\nJib currently builds into the [Docker V2.2](https://docs.docker.com/registry/spec/manifest-v2-2/) image format or [OCI image format](https://github.com/opencontainers/image-spec).\n\n#### Maven\n\nSee [Extended Usage](../jib-maven-plugin#extended-usage) for the `<container><format>` configuration.\n\n#### Gradle\n\nSee [Extended Usage](../jib-gradle-plugin#extended-usage) for the `container.format` configuration.\n\n### Why is my image created 48+ years ago?\n\nFor reproducibility purposes, Jib sets the creation time of the container images to the Unix epoch (00:00:00, January 1st, 1970 in UTC). If you would like to use a different timestamp, set the `jib.container.creationTime` / `<container><creationTime>` parameter to an ISO 8601 date-time. You may also use the value `USE_CURRENT_TIMESTAMP` to set the creation time to the actual build time, but this sacrifices reproducibility since the timestamp will change with every build.\n\n<details>\n<summary>Setting <code>creationTime</code> parameter (click to expand)</summary>\n<p>\n\n#### Maven\n\n```xml\n<configuration>\n  <container>\n    <creationTime>2019-07-15T10:15:30+09:00</creationTime>\n  </container>\n</configuration>\n```\n\n#### Gradle\n\n```groovy\njib.container.creationTime = '2019-07-15T10:15:30+09:00'\n```\n\n</p>\n</details>\n\nNote that the modification time of the files in the built image put by Jib will still be 1 second past the epoch. The file modification time can be configured using [`<container><filesModificationTime>`](../jib-maven-plugin#container-object) (Maven) or [`jib.container.filesModificationTime`](../jib-gradle-plugin#container-closure) (Gradle).\n\n#### Please tell me more about reproducibility!\n\n_Reproducible_ means that given the same inputs, a build should produce the same outputs.  Container images are uniquely identified by a digest (or a hash) of the image contents and image metadata.  Tools and infrastructure such the Docker daemon, Docker Hub, registries, Kubernetes, etc) treat images with different digests as being different.\n\nTo ensure that a Jib build is reproducible — that the rebuilt container image has the same digest — Jib adds files and directories in a consistent order, and sets consistent creation- and modification-times and permissions for all files and directories.  Jib also ensures that the image metadata is recorded in a consistent order, and that the container image has a consistent creation time.  To ensure consistent times, files and directories are recorded as having a creation and modification time of 1 second past the Unix Epoch (1970-01-01 00:00:01.000 UTC), and the container image is recorded as being created on the Unix Epoch.  Setting `container.creationTime` to `USE_CURRENT_TIMESTAMP` and then rebuilding an image will produce a different timestamp for the image creation time, and so the container images will have different digests and appear to be different.\n\nFor more details see [reproducible-builds.org](https://reproducible-builds.org).\n\n### Where is the application in the container filesystem?\n\nJib packages your Java application into the following paths on the image:\n\n* `/app/libs/` contains all the dependency artifacts\n* `/app/resources/` contains all the resource files\n* `/app/classes/` contains all the classes files\n* the contents of the extra directory (default `src/main/jib`) are placed relative to the container's root directory (`/`)\n\n### How are Jib applications layered?\n\nJib makes use of [layering](https://containers.gitbook.io/build-containers-the-hard-way/#layers) to allow for fast rebuilds - it will only rebuild the layers containing files that changed since the previous build and will reuse cached layers containing files that didn't change. Jib organizes files in a way that groups frequently changing files separately from large, rarely changing files. For example, `SNAPSHOT` dependencies are placed in a separate layer from other dependencies, so that a frequently changing `SNAPSHOT` will not force the entire dependency layer to rebuild itself.\n\nJib applications are split into the following layers:\n\n* All other dependencies\n* Snapshot dependencies\n* Project dependencies\n* Resources\n* Classes\n* Each extra directory (`jib.extraDirectories` in Gradle, `<extraDirectories>` in Maven) builds to its own layer\n\n### Which base image (JDK) does Jib use?\n\n[`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin) by Adoptium (formerly [`adoptopenjdk`](https://hub.docker.com/_/adoptopenjdk)) and [`jetty`](https://hub.docker.com/_/jetty) (for WAR). See [Default Base Images in Jib](default_base_image.md) for details.\n\n### Can I learn more about container images?\n\nIf you'd like to learn more about container images, [@coollog](https://github.com/coollog) has a guide: [Build Containers the Hard Way](https://containers.gitbook.io/build-containers-the-hard-way/), which takes a deep dive into everything involved in getting your code into a container and onto a container registry.\n\n\n## Configuring Jib\n\n### How do I set parameters for my image at runtime?\n\n#### JVM Flags\n\nFor the default base image, you can use the `JAVA_TOOL_OPTIONS` environment variable (note that other JRE images may require using other environment variables):\n\nUsing Docker: `docker run -e \"JAVA_TOOL_OPTIONS=<JVM flags>\" <image name>`\n\nUsing Kubernetes:\n```yaml\napiVersion: v1\nkind: Pod\nspec:\n  containers:\n  - name: <name>\n    image: <image name>\n    env:\n    - name: JAVA_TOOL_OPTIONS\n      value: <JVM flags>\n```\nNote that many JVMs may only support a max length of **1024** characters for the `JAVA_TOOL_OPTIONS` environment variable, and anything longer than this may be cut off by the JVM.\n\nFor Java 9+, often you may want to use [`JDK_JAVA_OPTIONS`](https://stackoverflow.com/questions/52986487/what-is-the-difference-between-jdk-java-options-and-java-tool-options-when-using) instead of `JAVA_TOOL_OPTIONS`.\n\n#### Other Environment Variables\n\nUsing Docker: `docker run -e \"NAME=VALUE\" <image name>`\n\nUsing Kubernetes:\n```yaml\napiVersion: v1\nkind: Pod\nspec:\n  containers:\n  - name: <name>\n    image: <image name>\n    env:\n    - name: NAME\n      value: VALUE\n```\n\n#### Arguments to Main\n\nUsing Docker: `docker run <image name> <arg1> <arg2> <arg3>`\n\nUsing Kubernetes:\n```yaml\napiVersion: v1\nkind: Pod\nspec:\n  containers:\n  - name: <name>\n    image: <image name>\n    args:\n    - <arg1>\n    - <arg2>\n    - <arg3>\n```\n\nFor more information, see the [`JAVA_TOOL_OPTIONS` environment variable](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html), the [`docker run -e` reference](https://docs.docker.com/engine/reference/run/#env-environment-variables), and [defining environment variables for a container in Kubernetes](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/).\n\n### Can I define a custom entrypoint at runtime?\n\nNormally, the plugin sets a default entrypoint for java applications, or lets you configure a custom entrypoint using the `container.entrypoint` configuration parameter. You can also override the default/configured entrypoint by defining a custom entrypoint when running the container. See [`docker run --entrypoint` reference](https://docs.docker.com/engine/reference/run/#entrypoint-default-command-to-execute-at-runtime) for running the image with Docker and overriding the entrypoint command, or see [Define a Command and Arguments for a Container](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/) for running the image in a [Kubernetes](https://kubernetes.io/) Pod and overriding the entrypoint command.\n\n### <a name=\"i-want-to-containerize-an-executable-jar\"></a>I want to containerize a JAR.\n\nThe intention of Jib is to add individual class files, resources, and dependency JARs into the container instead of putting a JAR. This lets Jib choose an opinionated, optimal layout for the application on the container image, which also allows it to skip the extra JAR-packaging step.\n\nHowever, you can set `<containerizingMode>packaged` (Maven) or `jib.containerizingMode = 'packaged'` (Gradle) to containerize a JAR, but note that your application will always be run via `java -cp ... your.MainClass` (even if it is an executable JAR). Some disadvantages of setting `containerizingMode='packaged'`:\n\n- You need to run the JAR-packaging step (`mvn package` in Maven or the `jar` task in Gradle).\n- Reduced granularity in building and caching: if any of your Java source files or resource files are updated, not only the JAR has to be rebuilt, but the entire layer containing the JAR in the image has to be recreated and pushed to the destination.\n- If it is a fat or shaded JAR embedding all dependency JARs, you are duplicating the dependency JARs in the image. Worse, it results in far more reduced granularity in building and caching, as dependency JARs can be huge and all of them need to be pushed repeatedly even if they do not change.\n\nNote that for runnable JARs/WARs, currently Jib does not natively support creating an image that runs a JAR (or WAR) through `java -jar runnable.jar` (although it is not impossible to configure Jib to do so at the expense of more complex project setup.)\n\n### I need to RUN commands like `apt-get`.\n\nRunning commands like `apt-get` slows down the container build process. We **do not recommend or support** running commands as part of the build.\n\nHowever, if you need to run commands, you can build a custom image and configure Jib to use it as the base image.\n\n<details>\n<summary>Base image configuration examples (click to expand)</summary>\n<p>\n\n#### Maven\n\nIn [`jib-maven-plugin`](../jib-maven-plugin), you can then use this custom base image by adding the following configuration:\n\n```xml\n<configuration>\n  <from>\n    <image>custom-base-image</image>\n  </from>\n</configuration>\n```\n\n#### Gradle\n\nIn [`jib-gradle-plugin`](../jib-gradle-plugin), you can then use this custom base image by adding the following configuration:\n\n```groovy\njib.from.image = 'custom-base-image'\n```\n</p>\n</details>\n\n### Can I ADD a custom directory to the image?\n\nYes, using the _extra directories_ feature. See the [Maven](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#adding-arbitrary-files-to-the-image) and [Gradle](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#adding-arbitrary-files-to-the-image) docs for examples.\n\n### I need to add files generated during the build process to a custom directory on the image.\n\nIf the current extra directories design doesn't meet your needs (e.g. you need to set up the extra files directory with files generated during the build process), you can use additional goals/tasks to create the extra directory as part of your build.\n\n<details>\n<summary>File copying examples (click to expand)</summary>\n<p>\n\n#### Maven\n\nIn Maven, you can use the `maven-resources-plugin` to copy files to your extra directory. For example, if you generate files in `target/generated/files` and want to add them to `/my/files` on the container, you can add the following to your `pom.xml`:\n\n```xml\n<plugins>\n  ...\n  <plugin>\n    <artifact>jib-maven-plugin</artifact>\n    ...\n    <configuration>\n      <extraDirectories>\n        <paths>\n          <path>${project.basedir}/target/extra-directory/</path>\n        </paths>\n      </extraDirectories>\n    </configuration>\n  </plugin>\n  ...\n  <plugin>\n    <artifact>maven-resources-plugin</artifact>\n    <version>3.2.0</version>\n    <configuration>\n      <outputDirectory>${project.basedir}/target/extra-directory/my/files</outputDirectory>\n      <resources>\n        <resource>\n          <directory>${project.basedir}/target/generated/files</directory>\n        </resource>\n      </resources>\n    </configuration>\n  </plugin>\n  ...\n</plugins>\n```\n\nThe `copy-resources` goal will run automatically before compile, so if you are copying files from your build output to the extra directory, you will need to either set the life-cycle phase to `post-compile` or later, or run the goal manually:\n\n```sh\nmvn compile resources:copy-resources jib:build\n```\n\n#### Gradle\n\nThe same can be accomplished in Gradle by using a `Copy` task. In your `build.gradle`:\n\n```groovy\njib.extraDirectories.paths = ['build/extra-directory']\n\ntask setupExtraDir(type: Copy) {\n  from file('build/generated/files')\n  into file('build/extra-directory/my/files')\n}\ntasks.jib.dependsOn setupExtraDir\n```\n\nThe files will be copied to your extra directory when you run the `jib` task.\n\n</p>\n</details>\n\n### Can I build to a local Docker daemon?\n\nThere are several ways of doing this:\n\n- Use [`jib:dockerBuild` for Maven](../jib-maven-plugin#build-to-docker-daemon) or [`jibDockerBuild` for Gradle](../jib-gradle-plugin#build-to-docker-daemon) to build directly to your local Docker daemon.\n- Use [`jib:buildTar` for Maven](../jib-maven-plugin#build-an-image-tarball) or [`jibBuildTar` for Gradle](../jib-gradle-plugin#build-an-image-tarball) to build the image to a tarball, then use `docker load --input` to load the image into Docker (the tarball built with these commands will be located in `target/jib-image.tar` for Maven and `build/jib-image.tar` for Gradle by default).\n- [`docker pull`](https://docs.docker.com/engine/reference/commandline/pull/) the image built with Jib to have it available in your local Docker daemon.\n- Alternatively, instead of using a Docker daemon, you can run a local container registry, such as [Docker registry](https://docs.docker.com/registry/deploying/) or other repository managers, and point Jib to push to the local registry.\n\n### How do I enable debugging?\n\nUse the [`JAVA_TOOL_OPTIONS`](#how-do-i-set-parameters-for-my-image-at-runtime) to pass along debugging configuration arguments.  For example, to have the remote VM accept local debug connections on port 5005, but not suspend:\n```\n-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=localhost:5005\n```\n\nThen connect your debugger to port 5005 on your local host.  You can port-forward the container port to a localhost port for easy access.\n\nUsing Docker: `docker run -p 5005:5005 <image>`\n\nUsing Kubernetes: `kubectl port-forward <pod name> 5005:5005`\n\nBeware: in Java 8 and earlier, specifying only a port meant that the JDWP socket was open to all incoming connections which is insecure.  It is recommended to limit the debug port to localhost.\n\n\n### I would like to run my application with a javaagent.\n\nYou can run your container with a javaagent by placing it somewhere in the `src/main/jib/myfolder` directory to add it to the container's filesystem, then pointing to it using Jib's `container.jvmFlags` configuration.\n\n#### Maven\n\n```xml\n<configuration>\n  <container>\n    <jvmFlags>\n      <jvmFlag>-javaagent:/myfolder/agent.jar</jvmFlag>\n    </jvmFlags>    \n  </container>\n</configuration>\n```\n\n#### Gradle\n\n```groovy\njib.container.jvmFlags = ['-javaagent:/myfolder/agent.jar']\n```\n\nSee also:\n- [Can I ADD a custom directory to the image?](#can-i-add-a-custom-directory-to-the-image)\n- [Javaagent sample](https://github.com/GoogleContainerTools/jib/tree/master/examples/java-agent): dynamically downloads a javaagent during build\n- (Gradle) [javaagent-gradle-plugin](https://github.com/ryandens/javaagent-gradle-plugin#jib-integration): third-party Jib extension\n\n### How can I tag my image with a timestamp?\n\n#### Maven\n\nTo tag the image with a simple timestamp, add the following to your `pom.xml`:\n\n```xml\n<properties>\n  <maven.build.timestamp.format>yyyyMMdd-HHmmssSSS</maven.build.timestamp.format>\n</properties>\n```\n\nThen in the `jib-maven-plugin` configuration, set the `tag` to:\n\n```xml\n<configuration>\n  <to>\n    <image>my-image-name:${maven.build.timestamp}</image>\n  </to>\n</configuration>\n```\n\nYou can then use the same timestamp to reference the image in other plugins.\n\n#### Gradle\n\nTo tag the image with a timestamp, simply set the timestamp as the tag for `to.image` in your `jib` configuration. For example:\n\n```groovy\njib.to.image = 'gcr.io/my-gcp-project/my-app:' + System.nanoTime()\n```\n\n\n### What would a Dockerfile for a Jib-built image look like?\n\nA Dockerfile that performs a Jib-like build is shown below:\n\n```Dockerfile\n# Jib uses Adoptium Eclipse Temurin (formerly AdoptOpenJDK).\nFROM eclipse-temurin:11-jre\n\n# Multiple copy statements are used to break the app into layers,\n# allowing for faster rebuilds after small changes\nCOPY dependencyJars /app/libs\nCOPY snapshotDependencyJars /app/libs\nCOPY projectDependencyJars /app/libs\nCOPY resources /app/resources\nCOPY classFiles /app/classes\n\n# Jib's extra directory (\"src/main/jib\" by default) is used to add extra, non-classpath files\nCOPY src/main/jib /\n\n# Jib's default entrypoint when container.entrypoint is not set\nENTRYPOINT [\"java\", jib.container.jvmFlags, \"-cp\", \"/app/resources:/app/classes:/app/libs/*\", jib.container.mainClass]\nCMD [jib.container.args]\n```\n\nWhen unset, Jib will infer the value for `jib.container.mainClass`.\n\nSome plugins, such as the [Docker Prepare Gradle Plugin](https://github.com/gclayburg/dockerPreparePlugin), will even automatically generate a Docker context for your project, including a Dockerfile.\n\n### How can I inspect the image Jib built?\n\nTo inspect the image that is produced from the build using Docker, you can use commands such as `docker inspect your/image:tag` to view the image configuration, or you can also download the image using `docker save` to manually inspect the container image. Other tools, such as [dive](https://github.com/wagoodman/dive), provide nicer UI to inspect the image.\n\n### How do I specify a platform in the manifest list (or OCI index) of a base image?\n\nNewer Jib versions added an _incubating feature_ that provides support for selecting base images with the desired platforms from a manifest list. For example,\n\n```xml\n  <from>\n    <image>... image reference to a manifest list ...</image>\n    <platforms>\n      <platform>\n        <architecture>amd64</architecture>\n        <os>linux</os>\n      </platform>\n      <platform>\n        <architecture>arm64</architecture>\n        <os>linux</os>\n      </platform>\n    </platforms>\n  </from>\n```\n\n```gradle\njib.from {\n  image = '... image reference to a manifest list ...'\n  platforms {\n    platform {\n      architecture = 'amd64'\n      os = 'linux'\n    }\n    platform {\n      architecture = 'arm64'\n      os = 'linux'\n    }\n  }\n}\n```\n\nThe default when not specified is a single \"amd64/linux\" platform, whose behavior is backward-compatible.\n\nWhen multiple platforms are specified, Jib creates and pushes a manifest list (also known as a fat manifest) after building and pushing all the images for the specified platforms.\n\nAs an incubating feature, there are certain limitations:\n- OCI image indices are not supported (as opposed to Docker manifest lists).\n- Only `architecture` and `os` are supported. If the base image manifest list contains multiple images with the given architecture and os, the first image will be selected.\n- Does not support using a local Docker daemon or tarball image for a base image.\n- Does not support pushing to a Docker daemon (`jib:dockerBuild` / `jibDockerBuild`) or building a local tarball (`jib:buildTar` / `jibBuildTar`).\n\nMake sure to specify a manifest _list_ in `<from><image>` (whether by a tag name or a digest (`@sha256:...`)). For troubleshooting, you may want to check what platforms the manifest list contains. To view a manifest list, [enable experimental docker CLI](https://docs.docker.com/engine/reference/commandline/cli/#experimental-features) features and then run the [manifest inspect](https://docs.docker.com/engine/reference/commandline/manifest_inspect/) command.\n\n```\n$ docker manifest inspect openjdk:8\n{         \n   ...\n   // This confirms that openjdk:8 points to a manifest list.\n   \"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\",\n   \"manifests\": [\n      {\n         // This entry in the list points to the manifest for the ARM64/Linux manifest.\n         \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n         ...\n         \"digest\": \"sha256:1fbd49e3fc5e53154fa93cad15f211112d899a6b0c5dc1e8661d6eb6c18b30a6\",\n         \"platform\": {\n            \"architecture\": \"arm64\",\n            \"os\": \"linux\",\n            \"variant\": \"v8\"\n         }\n      }\n   ]\n}\n```\n\n### I want to exclude files from layers, have more fine-grained control over layers, change file ownership, etc.\n\nSee [\"Jib build plugins don't have the feature that I need\"](#jib-build-plugins-dont-have-the-feature-that-i-need).\n\n### Jib build plugins don't have the feature that I need.\n\nThe Jib build plugins have an extension framework that enables anyone to easily extend Jib's behavior to their needs. We maintain select [first-party](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party) plugins for popular use cases like [fine-grained layer control](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-layer-filter-extension-gradle) and [Quarkus support](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-quarkus-extension-gradle), but anyone can write and publish an extension. Check out the [jib-extensions](https://github.com/GoogleContainerTools/jib-extensions) repository for more information.\n\n### I am hitting Docker Hub rate limits. How can I configure registry mirrors?\n\nSee the [Maven](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#global-jib-configuration), [Gradle](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#global-jib-configuration) or [Jib CLI](https://github.com/GoogleContainerTools/jib/blob/master/jib-cli/README.md#global-jib-configuration) docs. Note that the example in the docs uses [Google's Docker Hub mirror on `mirror.gcr.io`](https://cloud.google.com/container-registry/docs/pulling-cached-images).\n\nStarting from Jib build plugins 3.0, Jib by default uses [base images on Docker Hub](default_base_image.md), so you may start to encounter the rate limits if you are not explicitly setting a base image.\n\nSome other alternatives to get around the rate limits:\n\n* Prevent Jib from accessing Docker Hub (after Jib cached a base image locally).\n   - **Pin to a specific base image using a SHA digest.** For example, `jib.from.image='eclipse-temurin:11-jre@sha256:...'`. If you are not setting a base image with a SHA digest (which is the case if you don't set `jib.from.image` at all), then every time Jib runs, it reaches out to the registry to check if the base image is up-to-date. On the other hand, if you pin to a specific image with a digest, then the image is immutable. Therefore, if Jib has cached the image once (by running Jib online once to fully cache the image), Jib will not reach out to the Docker Hub. See [this Stack Overflow answer](https://stackoverflow.com/a/61190005/1701388) for more details.\n   - (Maven/Gradle plugins only) **Do offline building.** Pass `--offline` to Maven or Gradle. Before that, you need to run Jib online once to cache the image. However, `--offline` means you cannot push to a remote registry. See [this Stack Overflow answer](https://stackoverflow.com/a/61190005/1701388) for more details.\n   - **Retrieve a base image from a local Docker daemon.** Store an image to your local Docker daemon, and set, say, `jib.from.image='docker://eclipse-temurin:11-jre'`. It can be slow for an initial build where Jib has to cache the image in Jib's format. For performance reasons, we usually recommend using an image on a registry.\n   - **Set up a local registry, store a base image, and read it from the local registry.** Setting up a local registry is as simple as running `docker run -d -p5000:5000 registry:2`, but nevertheless, the whole process is a bit involved.\n* Retry with increasing backoffs. For example, using the [`retry`](https://github.com/kadwanev/retry) tool.\n\n### Where is the global Jib configuration file and how I can configure it?\n\nSee the [Maven](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#global-jib-configuration), [Gradle](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#global-jib-configuration) or [Jib CLI](https://github.com/GoogleContainerTools/jib/blob/master/jib-cli/README.md#global-jib-configuration) docs.\n\n\n## Build Problems\n\n### <a name=\"registry-errors\"></a>How can I diagnose problems pulling or pushing from remote registries?\n\nThere are a few reasons why Jib may be unable to connect to a remote registry, including:\n\n- **Registry reports FORBIDDEN.** See [_What should I do when the registry responds with Forbidden or DENIED?_](#what-should-i-do-when-the-registry-responds-with-forbidden-or-denied)\n- **Registry reports UNAUTHORIZED.** See [_What should I do when the registry responds with UNAUTHORIZED?_](#what-should-i-do-when-the-registry-responds-with-unauthorized)\n- **Access requires a proxy.** See [_How do I configure a proxy?_](#how-do-i-configure-a-proxy) for details.\n- **The registry does not support HTTPS.** We do not pass authentication details on non-HTTPS connections, though this can be overridden with the `sendCredentialsOverHttp` system property, but it is not recommend  (_version 0.9.9_).\n- **The registry's SSL certificates have expired or are not trusted.**  We have a separate document on [handling registries that use self-signed certificates](self_sign_cert.md), which may also apply if the SSL certificate is signed by an untrusted Certificate Authority.  Jib supports an  `allowInsecureRegistries` flag to ignore SSL certificate validation, but it is not recommend (_version 0.9.9_).\n- **The registry does not support the [Docker Image Format V2 Schema 2](https://github.com/GoogleContainerTools/jib/issues/601)** (sometimes referred to as _v2-2_).  This problem is usually shown by failures wth `INVALID_MANIFEST` errors. Some registries can be configured to support V2-2 such as [Artifactory](https://www.jfrog.com/confluence/display/RTF/Docker+Registry#DockerRegistry-LocalDockerRepositories) and [OpenShift](https://docs.openshift.com/container-platform/3.9/install_config/registry/extended_registry_configuration.html#middleware-repository-acceptschema2). Other registries, such as Quay.io/Quay Enterprise, are in the process of adding support.\n\n\n### What should I do when the registry responds with Forbidden or DENIED?\n\nIf the registry returns `403 Forbidden` or `\"code\":\"DENIED\"`, it often means Jib successfully authenticated using your credentials but the credentials do not have permissions to pull or push images. Make sure your account/role has the permissions to do the operation.\n\nDepending on registry implementations, it is also possible that the registry actually meant you are not authenticated. See [What should I do when the registry responds with UNAUTHORIZED?](#what-should-i-do-when-the-registry-responds-with-unauthorized) to ensure you have set up credentials correctly.\n\n### What should I do when the registry responds with UNAUTHORIZED?\n\nIf the registry returns `401 Unauthorized` or `\"code\":\"UNAUTHORIZED\"`, it is often due to credential misconfiguration. Examples:\n\n* You did not configure auth information in the default places where Jib searches. (See also [Authentication Methods](https://github.com/GoogleContainerTools/jib/blob/master/jib-maven-plugin/README.md#authentication-methods)).\n\n   - Docker credential file (as generated by `docker login` or `podman login`) at\n     - `$XDG_RUNTIME_DIR/containers/auth.json`, `$XDG_CONFIG_HOME/containers/auth.json` or `$HOME/.config/containers/auth.json`\n     - `$DOCKER_CONFIG/config.json`\n     - `$HOME/.docker/config.json`\n\n     This is [one of the configuration files](https://docs.docker.com/engine/reference/commandline/cli/#configuration-files) for the `docker` or `podman` command line tool. See [configuration files document](https://docs.docker.com/engine/reference/commandline/cli/#configuration-files), [credential store](https://docs.docker.com/engine/reference/commandline/login/#credentials-store) and [credential helper](https://docs.docker.com/engine/reference/commandline/login/#credential-helpers) sections, and [this](https://github.com/GoogleContainerTools/jib/issues/101) for how to configure auth. For example, you can do `docker login` to save auth in `config.json`, but it is often recommended to configure a credential helper (also configurable in `config.json`).\n\n     If Jib was able to retrieve auth information from a Docker credential file, you should see a log message similar to `Using credentials from Docker config (/home/myuser/.docker/config.json)` where you can verify which credential file was picked up by Jib.\n\n   - Jib configurations\n\n      - Configuring credential helpers: [`<from/to><credHelper>`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#using-docker-credential-helpers) (Maven) / [`from/to.credHelper`](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#using-docker-credential-helpers) (Gradle)\n      - Specific credentials (not recommend): [`<from/to><auth><username>/<password>`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#using-specific-credentials) or in [`settings.xml`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#using-maven-settings) (Maven) / [`from/to.auth.username/password`](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#using-specific-credentials) (Gradle)\n      - These parameters can also be set through properties: [Maven](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#system-properties) / [Gradle](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#system-properties)\n\n* For Google Cloud Registry (GCR), the Container Registry API is not yet enabled for your project.\n   - You enable the API from [Cloud Console](https://console.cloud.google.com/flows/enableapi?apiid=containerregistry.googleapis.com) or with the following [Cloud SDK](https://cloud.google.com/sdk/docs) command: `gcloud services enable containerregistry.googleapis.com`\n\n* `$HOME/.docker/config.json` may also contain short-lived authorizations in the `auths` block that may have expired. In the case of Google Container Registry, if you had previously used `gcloud docker` to configure these authorizations, you should remove these stale authorizations by editing your `config.json` and deleting lines from `auths` associated with `gcr.io` (for example: `\"https://asia.gcr.io\"`). You can then run `gcloud auth configure-docker` to correctly configure the `credHelpers` block for more robust interactions with gcr.\n\n* Different auth configurations exist in multiple places, and Jib is not picking up the auth information you are working on.\n\n* You configured a credential helper, but the helper is not on `$PATH`. This is especially common when running Jib inside IDE where the IDE binary is launched directly from an OS menu and does not have access to your shell's environment.\n\n* Configured credentials have access to the base image repository but not to the target image repository (or vice versa).\n\n* Typos in username, password, image names, repository names, or registry names. This is a very common error.\n\n* Image names do not conform to the structure or policy that a registry requires. For example, [Docker Hub returns 401 Unauthorized](https://github.com/GoogleContainerTools/jib/issues/2650#issuecomment-667323777) when trying to use a multi-level repository name.\n\n* Incorrect port number in image references (`registry.hostname:<port>/...`).\n\n* You are using a private registry without HTTPS. See [How can I diagnose problems pulling or pushing from remote registries?](#how-can-i-diagnose-problems-pulling-or-pushing-from-remote-registries).\n\nNote, if Jib was able to retrieve credentials, you should see a log message like these:\n\n```\nUsing credentials from Docker config (/home/user/.docker/config.json) for localhost:5000/java\n```\n```\nUsing credential helper docker-credential-gcr for gcr.io/project/repo\n```\n```\nUsing credentials from Maven settings file for gcr.io/project/repo\n```\n```\nUsing credentials from <from><auth> for gcr.io/project/repo\n```\n```\nUsing credentials from to.auth for gcr.io/project/repo\n```\n\nIf you encounter issues interacting with a registry other than `UNAUTHORIZED`, check [\"How can I diagnose problems pulling or pushing from remote registries?\"](#how-can-i-diagnose-problems-pulling-or-pushing-from-remote-registries).\n\n### How do I configure a proxy?\n\nJib currently requires configuring your build tool to use the appropriate [Java networking properties](https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html) (`https.proxyHost`, `https.proxyPort`, `https.proxyUser`, `https.proxyPassword`).\n\n\n### How can I examine network traffic?\n\nIt can be useful to examine network traffic to diagnose connectivity issues. Jib uses the Google HTTP client library to interact with registries which logs HTTP requests using the JVM-provided `java.util.logging` facilities.  It is very helpful to serialize Jib's actions using the `jib.serialize` property.\n\nTo see the HTTP traffic, create a `logging.properties` file with the following:\n```\nhandlers = java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.level=ALL\n\n# CONFIG hides authentication data\n# ALL includes authentication data\ncom.google.api.client.http.level=CONFIG\n```\n\nAnd then launch your build tool as follows:\n```sh\nmvn --batch-mode -Djava.util.logging.config.file=path/to/logging.properties -Djib.serialize=true ...\n```\nor\n```sh\ngradle --no-daemon --console=plain --info -Djava.util.logging.config.file=path/to/logging.properties -Djib.serialize=true ...\n```\n\n**Note**: Jib Gradle plugins prior to version 2.2.0 have an issue generating HTTP logs ([#2356](https://github.com/GoogleContainerTools/jib/issues/2356)).\n\nYou may want to enable the debug logs too (`-X` for Maven, or `--debug --stacktrace` for Gradle).\n\nWhen configured correctly, you should see logs like this:\n```\nMar 31, 2020 9:55:52 AM com.google.api.client.http.HttpResponse <init>\nCONFIG: -------------- RESPONSE --------------\nHTTP/1.1 202 Accepted\nContent-Length: 0\nDocker-Distribution-Api-Version: registry/2.0\nDocker-Upload-Uuid: 6292f0d7-93cb-4a8e-8336-78a1bf7febd2\nLocation: https://registry-1.docker.io/v2/...\nRange: 0-657292\nDate: Tue, 31 Mar 2020 13:55:52 GMT\nStrict-Transport-Security: max-age=31536000\n\nMar 31, 2020 9:55:52 AM com.google.api.client.http.HttpRequest execute\nCONFIG: -------------- REQUEST  --------------\nPUT https://registry-1.docker.io/v2/...\nAccept:\nAccept-Encoding: gzip\nAuthorization: <Not Logged>\nUser-Agent: jib 2.1.1-SNAPSHOT jib-maven-plugin Google-HTTP-Java-Client/1.34.0 (gzip)\n```\n\n### How do I view debug logs for Jib?\n\nMaven: use `mvn -X -Djib.serialize=true` to enable more detailed logging and serialize Jib's actions.\n\nGradle: use `gradle --debug -Djib.serialize=true` to enable more detailed logging and serialize Jib's actions.\n\n### I am seeing `Method Not Found` or `Class Not Found` errors when building.\n\nSometimes when upgrading your Gradle build plugin versions, you may experience errors due to mismatching versions of dependencies pulled in (for example: [issues/2183](https://github.com/GoogleContainerTools/jib/issues/2183)). This can be due to the buildscript classpath loading behavior described [on gradle forums](https://discuss.gradle.org/t/version-is-root-build-gradle-buildscript-is-overriding-subproject-buildscript-dependency-versions/20746/3). \n\nThis commonly appears in multi module Gradle projects. A solution to this problem is to define all of your plugins in the base project and apply them selectively in your subprojects as needed. This should help alleviate the problem of the buildscript classpath using older versions of a library.\n\n`build.gradle` (root)\n```groovy\nplugins {\n  id 'com.google.cloud.tools.jib' version 'x.y.z' apply false\n}\n```\n\n`build.gradle` (sub-project)\n```groovy\nplugins {\n  id 'com.google.cloud.tools.jib'\n}\n```\n\n### I am seeing `Unsupported class file major version` when building.\n\nWhen you're using latest Java versions to write an app (or using an old version of Jib), you may see the error _coming from Jib when building an image_ (not when compiling your code):\n\n```\nFailed to execute goal com.google.cloud.tools:jib-maven-plugin:3.2.0:dockerBuild (default-cli) on project demo: Execution default-cli of goal com.google.cloud.tools:jib-maven-plugin:3.2.0:dockerBuild failed: Unsupported class file major version 61\n```\n\nJib uses the [ASM library](https://asm.ow2.io/) to examine compiled Java bytecode to automatically infer a main class (in other words, the class that defines `public static void main()` to start your app). In this way, if you have only one such class, Jib can automatically infer and use that class to set an image entrypoint (basically, a command to start your app). When new Java versions come out, often the ASM library version used in Jib doesn't support the new bytecode format. If this is the case, check if you are using the latest Jib. If you still get the error with the latest Jib, file a [bug](https://github.com/GoogleContainerTools/jib/issues/new/choose) to have the Jib team upgrade the ASM library.\n\n**Workaround**: to prevent Jib from doing auto-inference, you can manually set your desired main class via [`<container><mainClass>`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#container-object) (for example, `<container><mainClass>com.example.your.Main</mainClass>`). As with other Jib parameters, it can be set through system/Maven properties or on the command-line (for example, `-Dcontainer.mainClass=...`).\n\nNote that although the ASM library is the common cause of this error coming from Jib, it may be due to other reasons. Always check the full stack (`-e` or `-X` for Maven and `--stacktrace` for Gradle) to see where the error is coming from.\n\n### I am seeing `NoClassDefFoundError: com/github/luben/zstd/ZstdOutputStream` when building.\n\nJib supports base image layers with media-type `application/vnd.oci.image.layer.v1.tar+zstd`, i.e. compressed with zstd algorithm instead of gzip.\n\nHowever, the dependency to zstd is optional, so pulling such layers will result in:\n\n```\njava.lang.NoClassDefFoundError: com/github/luben/zstd/ZstdOutputStream\nat org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream.<init>\n```\n\nThis can be solved by adding a dependency to artifact `com.github.luben:zstd-jni:1.5.2-3` to the plugin.\n\n## Launch problems\n\n### I am seeing `ImagePullBackoff` on my pods (in [minikube](https://github.com/kubernetes/minikube)).\n\nWhen you use your private image built with Jib in a [Kubernetes cluster](kubernetes.io), the cluster needs to be configured with credentials to pull the image. This involves 1) creating a [Secret](https://kubernetes.io/docs/concepts/configuration/secret/), and 2) using the Secret as [`imagePullSecrets`](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account).\n\n```shell\nkubectl create secret docker-registry registry-json-key \\\n  --docker-server=<registry> \\\n  --docker-username=<username> \\\n  --docker-password=<password> \\\n  --docker-email=<any valid email address>\n\nkubectl patch serviceaccount default \\\n  -p '{\"imagePullSecrets\":[{\"name\":\"registry-json-key\"}]}'\n```\n\nFor example, if you are using GCR, the commands would look like (see [Advanced Authentication Methods](https://cloud.google.com/container-registry/docs/advanced-authentication)):\n\n```shell\nkubectl create secret docker-registry gcr-json-key \\\n  --docker-server=https://gcr.io \\\n  --docker-username=_json_key \\\n  --docker-password=\"$(cat keyfile.json)\" \\\n  --docker-email=any@valid.com\n\nkubectl patch serviceaccount default \\\n  -p '{\"imagePullSecrets\":[{\"name\":\"gcr-json-key\"}]}'\n```\n\nSee more at [Using Google Container Registry (GCR) with Minikube](https://ryaneschinger.com/blog/using-google-container-registry-gcr-with-minikube/).\n\n### Why won't my container start?\n\nThere are some common reasons why containers fail on launch.\n\n#### My shell script won't run.\n\nJib Maven and Gradle plugins prior to 3.0 used Distroless Java as the default base image, which does not have a shell. See [Where is bash?](#where-is-bash) for more details.\n\n#### The container fails with `exec` errors.\n\nA Jib user reported an error launching their container:\n```\nstandard_init_linux.go:211 exec user process caused \"no such file or directory\"\n```\n\nOn examining the container structure with [Dive](https://github.com/wagoodman/dive), the user discovered that the contents of the `/lib` directory had disappeared.\n\nThe user had used Jib's ability to install extra files into the image ([Maven](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#adding-arbitrary-files-to-the-image), [Gradle](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#adding-arbitrary-files-to-the-image)) to install a library file by placing it in `src/main/jib/lib/libfoo.so`. This would normally cause the `libfoo.so` to be installed in the image as `/lib/libfoo.so`. But `/lib` and `/lib64` in the user's base image were symbolic links. Jib does not follow such symbolic links when creating the image. And at container initialization time, Docker treats these symlinks as a file, and thus the symbolic link was replaced with `/lib` as a new directory. As a result, none of the system shared libraries were resolved and dynamically-linked programs failed.\n\nSolution: The user installed the file in a different location.\n\n## Jib CLI \n \n### How does the `jar` command support Standard JARs?\n\nThe Jib CLI supports both [thin JARs](https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html) (where dependencies are specified in the JAR's manifest) and fat JARs.\n\nThe current limitation of using a fat JAR is that the embedded dependencies will not be placed into the designated dependencies layers. They will instead be placed into the classes or resources layer. Therefore, for efficiency, we recommend against containerizing fat JARs (Spring Boot fat JARs are an [exception](#how-does-the-jar-command-support-spring-boot-jars)) if you can prepare thin JARs. We hope to have better support for fat JARs in the future.\n\nA standard JAR can be containerized by the `jar` command in two modes, exploded or packaged. \n\n#### Exploded Mode (Recommended)\n\nAchieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar`\n\nThe default mode for containerizing a JAR. It will open up the JAR and optimally place files into the following layers:  \n\n* Other Dependencies Layer\n* Snapshot-Dependencies Layer\n* Resources Layer\n* Classes Layer\n\n**Entrypoint** : `java -cp /app/dependencies/:/app/explodedJar/ ${MAIN_CLASS}`\n\n#### Packaged Mode\n\nAchieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar --mode packaged`.\n\nIt will result in the following layers on the container:\n\n* Dependencies Layer\n* Jar Layer\n\n**Entrypoint** : `java -jar ${JAR_NAME}.jar`\n\n### How does the `jar` command support Spring Boot JARs?\n\nThe `jar` command currently supports containerization of Spring Boot fat JARs.\nA Spring-Boot fat JAR can be containerized in two modes, exploded or packaged. \n\n#### Exploded Mode (Recommended)\n\nAchieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar`\n\nThe default mode for containerizing a JAR. It will respect [`layers.idx`](https://spring.io/blog/2020/08/14/creating-efficient-docker-images-with-spring-boot-2-3) in the JAR (if present) or create optimized layers in the following format:\n\n* Other Dependencies Layer\n* Spring-Boot-Loader Layer\n* Snapshot-Dependencies Layer\n* Resources Layer\n* Classes Layer\n\n**Entrypoint** : `java -cp /app org.springframework.boot.loader.JarLauncher`\n\n#### Packaged Mode\n\nAchieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar --mode packaged`\n\nIt will containerize the JAR as is. However, **note** that we highly recommend against using packaged mode for containerizing Spring Boot fat JARs. \n\n**Entrypoint**: `java -jar ${JAR_NAME}.jar`\n\n### How does the `war` command work?\n\nThe `war` command currently supports containerization of standard WARs. It uses the official [`jetty`](https://hub.docker.com/_/jetty) on Docker Hub as the default base image and explodes out the WAR into `/var/lib/jetty/webapps/ROOT` on the container. It creates the following layers:\n\n* Other Dependencies Layer\n* Snapshot-Dependencies Layer\n* Resources Layer\n* Classes Layer\n\nThe default entrypoint when using a jetty base image will be `java -jar /usr/local/jetty/start.jar --module=ee10-deploy` unless you choose to specify a custom one.\n\nYou can use a different Servlet engine base image with the help of the `--from` option and customize `--app-root`, `--entrypoint` and `--program-args`. If you don't set the `entrypoint` or `program-arguments`, Jib will inherit them from the base image. However, setting the `--app-root` is **required** if you use a non-jetty base image. Here is how the `war` command may look if you're using a Tomcat image:\n```\n $ jib war --target=<image-reference> myapp.war --from=tomcat:8.5-jre8-alpine --app-root=/usr/local/tomcat/webapps/ROOT\n```\n \n"
  },
  {
    "path": "docs/google-cloud-build.md",
    "content": "# Jib on Google Cloud Build\n\nYou can use Jib on [Google Cloud Build](https://cloud.google.com/build) in a simple step:\n\n```yaml\nsteps:\n  - name: 'gcr.io/cloud-builders/javac:8'\n    entrypoint: './gradlew'\n    args: ['--console=plain', '--no-daemon', ':server:jib', '-Djib.to.image=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA']\n```\n\nAny Java container can be used for building, not only the `gcr.io/cloud-builders/javac:*` (from [gcr.io/cloud-builders/javac](https://github.com/GoogleCloudPlatform/cloud-builders/tree/master/javac)), for example with [Temurin](https://adoptium.net/en-GB/temurin/)'s:\n\n```yaml\nsteps:\n  - name: 'docker.io/library/eclipse-temurin:25'\n    entrypoint: './gradlew'\n    args: ['--console=plain', '--no-daemon', ':server:jib', '-Djib.to.image=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA']\n```\n\nTo use [Google \"Distroless\" Container Images](https://github.com/GoogleContainerTools/distroless) to build with Jib on Google Cloud Build, and avoid running into `Step #1: standard_init_linux.go:228: exec user process caused: no such file or directory` errors (because Google's _distroless_ containers are based on `busybox`), you have to do something like this:\n\n```yaml\nsteps:\n  - name: 'gcr.io/distroless/java17-debian11:debug'\n    entrypoint: '/busybox/sh'\n    args:\n      - -c\n      - |\n        ln -s /busybox/sh /bin/sh\n        ln -s /busybox/env /usr/bin/env\n        /workspace/gradlew --console=plain --no-daemon --gradle-user-home=/home/.gradle :server:jib -Djib.to.image=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA\n```\n"
  },
  {
    "path": "docs/privacy.md",
    "content": "The privacy of our users is very important to us.\nYour use of this software is subject to the <a href=https://policies.google.com/privacy>Google Privacy Policy</a>.\n\n## Update check\nMany Jib users are unaware of new releases. To encourage users to stay up-to-date, the Jib Maven and Jib Gradle plugins (2.0.0 and later) and Jib CLI (0.6.0 and later) will\nperiodically check to see if there is a new version of Jib is available. This check fetches a simple text\nfile hosted in Google Cloud Storage. As a side effect this request is logged, which includes the request path,\nsource IP address, and the user-agent string. The user-agent is set by Jib and includes the Jib tool name\nand version.\n\n### How to disable update checks\n\n1. set the `jib.disableUpdateChecks` system property to `true`\n2. set `disableUpdateCheck` to `true` in Jib's global config. The global config is in the following locations by default:\n    * Linux: `$XDG_CONFIG_HOME/google-cloud-tools-java/jib/config.json` (if `$XDG_CONFIG_HOME` is defined), else `$HOME/.config/google-cloud-tools-java/jib/config.json`\n    * Mac: `$XDG_CONFIG_HOME/Google/Jib/config.json` (if `$XDG_CONFIG_HOME` is defined), else `$HOME/Library/Preferences/Google/Jib/config.json`\n    * Windows: `$XDG_CONFIG_HOME\\Google\\Jib\\Config\\config.json` (if `$XDG_CONFIG_HOME` is defined), else `%LOCALAPPDATA%\\Google\\Jib\\Config\\config.json`"
  },
  {
    "path": "docs/self_sign_cert.md",
    "content": "# Accessing a private docker registry with self-signed certificate\n\nJib relies on the Java Runtime Environment's list of approved _Certification Authority Certificates_ for validating SSL certificates, and will hence fail when connecting to a docker registry that uses a self-signed `https` certificate.  This document describes two approaches for handling registries with self-signed certificates.  Both approaches configure the JRE's list of approved CA Certificates.\n\nThese CA Certificates for JRE are managed through a type of a keystore file called _truststore_. An easy way to manipulate truststores is using the [KeyStore Explorer](http://keystore-explorer.org/), an open source GUI replacement for the Java command-line  `keytool` and `jarsigner` utilities. Download and install KeyStore Explorer from the [official website](http://keystore-explorer.org/downloads.html).\n\n\n## Step 1. Identify Java runtime used by build tool\n\nWe must first identify the location of your build-tool's JRE's list of CA Certificates.\n\n### Maven\n\nRun `mvn --version` and take note of the Java runtime location:\n\n```shell\n$ mvn --version\nApache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T06:33:14+12:00)\nMaven home: /usr/local/Cellar/maven/3.5.4/libexec\nJava version: 1.8.0_172, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre\nDefault locale: en_NZ, platform encoding: UTF-8\nOS name: \"mac os x\", version: \"10.13.6\", arch: \"x86_64\", family: \"mac\"\n```\n\nIn this example the Java runtime location is `/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre`.\n\n### Gradle\n\nCreate an init script with the following:\n\n```\nprintln org.gradle.internal.jvm.Jvm.current().getJavaHome()\n```\n\nAnd run `gradle -I /path/to/script` to output the executing JRE location.\n\n```shell\n$ gradle -I /tmp/printjrelocation\n/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home\n\n> Task :help \n\nWelcome to Gradle 4.6.\n[...]\n```\n\n### JRE vs JDK Distributions\n\nThe Maven and Gradle examples above report two different directories, where the Maven example reported a `.../jre` subdirectory.  Java Development Kits usually include a standalone Java Runtime Environment inside the `jre/` directory.  If present, use the `jre/` directory as the runtime location.\n\n## 2. Load JRE CA Certificates\n\nHaving identified your Java runtime location:\n\n* Launch `KeyStore Explorer`\n* Select _Open an existing KeyStore_\n* Navigate to the Java runtime location identified previously, and then continue to open the file at `jre/lib/security/cacerts`.  If there is no `jre/` directory then this is a JRE distribution and should navigate and instead open the file at `lib/security/cacerts`.\n  * In the example above, this file would be `/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib/security/cacerts`.\n* You will likely be prompted for a password. The default password for the `cacerts` file is `changeit`.\n\n## 3. Import Self-Signed Certificate\n\nIf you have the self-signed certificate in a file then:\n\n* Select _Tools > Import Trusted Certificate_\n* Select the certifcate file on disk\n* Give it a name, or use suggested name, and click _OK_\n* Click _OK_ on the success window\n\nOtherwise use _Examine > Examine SSL_ to connect to your service and click the _Import_ button to import its SSL certificate. Then click _OK_.\n\n![Importing certificate with KeyStore Explorer](self_sign_cert-kse-import.png)\n\n## 4. Save the CA Certificates\n\nNow we save the updated truststore. We can either save to a new truststore and configure our build's JVM to use this new truststore, or modify the JRE's list of CA Certificates.\n\n#### Option 1: Create a New Truststore\n\nThis option creates a _new_ list of CA Certificates and configures your build tool to use this new list as the JRE's list of approved CA certificates.\n\nWithin _KeyStore Explorer_, select _File > Save As..._ and save the new truststore file as a _JKS_ file within your project location. You will be prompted for a password; we use `password` in the examples below.\n\n##### Maven\n\nThe following snippet shows how to configure Maven to use this new truststore file:\n\n```shell\n$ ./mvnw -Djavax.net.ssl.trustStore=path/to/truststore.jks \\\n  -Djavax.net.ssl.trustStorePassword=password \\\n  -Dimage=<host>:<port>/<image> jib:build\n```\n\n##### Gradle\n\nThe following snippet shows how to configure Gradle to use this new truststore file:\n\n```shell\n$ ./gradlew jib \\\n  -Djavax.net.ssl.trustStore=path/to/truststore.jks \\\n  -Djavax.net.ssl.trustStorePassword=password\n```\n\n#### Option 2: Modify the JRE `cacerts`\n\nThe other approach modifies the JRE's list of CA Certificates to include the registry's self-signed certificate.  The certificate will be trusted at the JRE level, affecting all Java applications running on it. You must re-import the certificate when you update to a new JRE.\n\nBasically you instruct KeyStore Explorer to save your modified `cacerts` and replace what was previously configured with the JRE.  Depending on your operating system and permissions, you may need to save to a new file and then replace the original `lib/security/cacerts` file with administrative privileges.\n\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Example projects containerizing with Jib\n\nPlease [file an issue](/../../issues/new) if you find any problems with the examples or would like to request other examples.\n\nFor examples on using Jib Core, see [jib-core/examples](../jib-core/examples).\n\n### Simple example\n\nSee [helloworld](helloworld) for containerizing a simple `Hello World` application.\n\n### Multi-module example (DRAFT)\n\nSee [multi-module](multi-module) for containerizing projects with multiple modules.\n\n### Vert.x\n\nSee [vertx](vertx) for containerizing [Eclipse Vert.x](https://vertx.io/) applications.\n\n### Spring Boot example\n\nSee [spring-boot](spring-boot) for containerizing a [Spring Boot](https://spring.io/projects/spring-boot) application and running it on [Kubernetes](https://kubernetes.io).\n\n### Ktor example\n\nSee [ktor](ktor) for containerizing a [Ktor](https://ktor.io) Kotlin Application using the Kotlin Gradle DSL.\n\n### Dropwizard example\n\nSee [dropwizard](dropwizard) for containerizing [Dropwizard](https://dropwizard.io) applications.\n\n### Micronaut example\n\nSee [micronaut](micronaut) for containerizing a [Micronaut framework](https://micronaut.io/) Groovy/Java application.\n\n### Java agents example\n\nSee [java-agent](java-agent) for launching with the Stackdriver Debugger Java agent.\n\n### Kafka Connect+Streams example\n\nSee the following projects for using Jib-packaged applications in coordination with [Apache Kafka](http://kafka.apache.org/documentation).\n  - [Kafka Streams](http://kafka.apache.org/documentation/streams) - [`OneCricketeer/kafka-streams-jib-example`](https://github.com/OneCricketeer/kafka-streams-jib-example)\n  - [Kafka Connect](http://kafka.apache.org/documentation#connect) - [`OneCricketeer/apache-kafka-connect-docker`](https://github.com/OneCricketeer/apache-kafka-connect-docker)\n"
  },
  {
    "path": "examples/dropwizard/.mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\nLicensed to the Apache Software Foundation (ASF) under one\nor more contributor license agreements.  See the NOTICE file\ndistributed with this work for additional information\nregarding copyright ownership.  The ASF licenses this file\nto you under the Apache License, Version 2.0 (the\n\"License\"); you may not use this file except in compliance\nwith the License.  You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing,\nsoftware distributed under the License is distributed on an\n\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, either express or implied.  See the License for the\nspecific language governing permissions and limitations\nunder the License.\n*/\n\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL =\n            \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: : \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output direcrory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": "examples/dropwizard/.mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip"
  },
  {
    "path": "examples/dropwizard/README.md",
    "content": "# Containerize a [Dropwizard](https://dropwizard.io) application with Jib\n\n## How to start the Dropwizard application\n\n1. Run `./mvnw clean package` to build your container\n1. Start the application\n    - **With Docker**: `docker run --rm -p 8080:8080 dropwizard-jib-example:1`\n    - **Without Docker**: `./mvnw exec:java`\n1. Check that your application is running at http://localhost:8080\n\n## Health Check\n\nSee your application's health at http://localhost:8080/admin/healthcheck\n\n## Extras\n\nFreeMaker templating is setup for [`dropwizard.yml`](src/main/resources/dropwizard.yml) through [`tkrille/dropwizard-template-config`](https://github.com/tkrille/dropwizard-template-config); this allows one to heavily customize the properties file via the container environment with FTL conditional checks and for loops, for example.\n\n## How this example was generated\n\nStarter Maven template generated with [`dropwizard-archetypes`](https://github.com/dropwizard/dropwizard/tree/master/dropwizard-archetypes)\n\n```sh\nmvn archetype:generate \\\n  -DarchetypeGroupId=io.dropwizard.archetypes \\\n  -DarchetypeArtifactId=example \\\n  -DarchetypeVersion=[REPLACE ME WITH A VALID DROPWIZARD VERSION]\n```\n\nRef. [Dropwizard - Getting Started, Setting up With Maven](https://www.dropwizard.io/1.3.5/docs/getting-started.html#setting-up-using-maven)\n\nThe remainder of the archetype code was filled-in following the above guide.\n\n## More information\n\nLearn [more about Jib](https://github.com/GoogleContainerTools/jib).\n\nLearn [more about Dropwizard](https://dropwizard.io).\n\n## Build and run on Google Cloud\n\n[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run?git_repo=https://github.com/GoogleContainerTools/jib.git&dir=examples/dropwizard)\n"
  },
  {
    "path": "examples/dropwizard/mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar\"\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        wget \"$jarUrl\" -O \"$wrapperJarPath\"\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        curl -o \"$wrapperJarPath\" \"$jarUrl\"\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "examples/dropwizard/mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Maven2 Start Up Batch script\r\n@REM\r\n@REM Required ENV vars:\r\n@REM JAVA_HOME - location of a JDK home dir\r\n@REM\r\n@REM Optional ENV vars\r\n@REM M2_HOME - location of maven2's installed home dir\r\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\r\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r\n@REM     e.g. to debug Maven itself, use\r\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r\n@echo off\r\n@REM set title of command window\r\ntitle %0\r\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\r\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\r\n\r\n@REM set %HOME% to equivalent of $HOME\r\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\r\n\r\n@REM Execute a user defined script before this one\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\r\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\r\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\r\n:skipRcPre\r\n\r\n@setlocal\r\n\r\nset ERROR_CODE=0\r\n\r\n@REM To isolate internal variables from possible post scripts, we use another setlocal\r\n@setlocal\r\n\r\n@REM ==== START VALIDATION ====\r\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\r\n\r\necho.\r\necho Error: JAVA_HOME not found in your environment. >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n:OkJHome\r\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\r\n\r\necho.\r\necho Error: JAVA_HOME is set to an invalid directory. >&2\r\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n@REM ==== END VALIDATION ====\r\n\r\n:init\r\n\r\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\r\n@REM Fallback to current working directory if not found.\r\n\r\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\r\n\r\nset EXEC_DIR=%CD%\r\nset WDIR=%EXEC_DIR%\r\n:findBaseDir\r\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\r\ncd ..\r\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\r\nset WDIR=%CD%\r\ngoto findBaseDir\r\n\r\n:baseDirFound\r\nset MAVEN_PROJECTBASEDIR=%WDIR%\r\ncd \"%EXEC_DIR%\"\r\ngoto endDetectBaseDir\r\n\r\n:baseDirNotFound\r\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r\ncd \"%EXEC_DIR%\"\r\n\r\n:endDetectBaseDir\r\n\r\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\r\n\r\n@setlocal EnableExtensions EnableDelayedExpansion\r\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r\n\r\n:endReadAdditionalConfig\r\n\r\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\r\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\r\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r\n\r\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar\"\r\nFOR /F \"tokens=1,2 delims==\" %%A IN (%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties) DO (\r\n\tIF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B \r\n)\r\n\r\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\r\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\r\nif exist %WRAPPER_JAR% (\r\n    echo Found %WRAPPER_JAR%\r\n) else (\r\n    echo Couldn't find %WRAPPER_JAR%, downloading it ...\r\n\techo Downloading from: %DOWNLOAD_URL%\r\n    powershell -Command \"(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"\r\n    echo Finished downloading %WRAPPER_JAR%\r\n)\r\n@REM End of extension\r\n\r\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r\nif ERRORLEVEL 1 goto error\r\ngoto end\r\n\r\n:error\r\nset ERROR_CODE=1\r\n\r\n:end\r\n@endlocal & set ERROR_CODE=%ERROR_CODE%\r\n\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\r\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\r\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\r\n:skipRcPost\r\n\r\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\r\n\r\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\r\n\r\nexit /B %ERROR_CODE%\r\n"
  },
  {
    "path": "examples/dropwizard/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project\n        xmlns=\"http://maven.apache.org/POM/4.0.0\"\n        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>example</groupId>\n    <artifactId>dropwizard-jib-example</artifactId>\n    <version>1</version>\n    <packaging>jar</packaging>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n\n        <!-- for exec-maven-plugin -->\n        <mainClass>example.JibExampleApplication</mainClass>\n\n        <dropwizard.version>1.3.16</dropwizard.version>\n        <dropwizard.server.config>dropwizard.yml</dropwizard.server.config>\n        <dropwizard-template-config.version>1.5.0</dropwizard-template-config.version>\n\n        <jib.container.appRoot>/app</jib.container.appRoot>\n        <jib-maven-plugin.version>3.5.1</jib-maven-plugin.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>io.dropwizard</groupId>\n                <artifactId>dropwizard-bom</artifactId>\n                <version>${dropwizard.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-core</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>de.thomaskrille</groupId>\n            <artifactId>dropwizard-template-config</artifactId>\n            <version>${dropwizard-template-config.version}</version>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>com.google.cloud.tools</groupId>\n                <artifactId>jib-maven-plugin</artifactId>\n                <version>${jib-maven-plugin.version}</version>\n                <configuration>\n                    <container>\n                        <args>\n                            <arg>server</arg>\n                            <arg>${jib.container.appRoot}/resources/${dropwizard.server.config}</arg>\n                        </args>\n                        <ports>\n                            <port>8080</port>\n                        </ports>\n\n                        <!-- good defaults intended for Java 8 (>= 8u191) containers -->\n                        <jvmFlags>\n                            <jvmFlag>-server</jvmFlag>\n                            <jvmFlag>-Djava.awt.headless=true</jvmFlag>\n                            <jvmFlag>-XX:InitialRAMFraction=2</jvmFlag>\n                            <jvmFlag>-XX:MinRAMFraction=2</jvmFlag>\n                            <jvmFlag>-XX:MaxRAMFraction=2</jvmFlag>\n                            <jvmFlag>-XX:+UseG1GC</jvmFlag>\n                            <jvmFlag>-XX:MaxGCPauseMillis=100</jvmFlag>\n                            <jvmFlag>-XX:+UseStringDeduplication</jvmFlag>\n                        </jvmFlags>\n                    </container>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>dockerBuild</id>\n                        <goals>\n                            <goal>dockerBuild</goal>\n                        </goals>\n                        <phase>package</phase>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>exec-maven-plugin</artifactId>\n                <version>1.6.0</version>\n                <configuration>\n                    <mainClass>${mainClass}</mainClass>\n                    <arguments>\n                        <argument>server</argument>\n                        <argument>${project.build.sourceDirectory}/../resources/${dropwizard.server.config}</argument>\n                    </arguments>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "examples/dropwizard/src/main/java/example/JibExampleApplication.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport de.thomaskrille.dropwizard_template_config.TemplateConfigBundle;\nimport io.dropwizard.Application;\nimport io.dropwizard.setup.Bootstrap;\nimport io.dropwizard.setup.Environment;\n\nimport example.health.TemplateHealthCheck;\nimport example.resources.HelloWorldResource;\n\n/**\n * <a href=\"https://www.dropwizard.io/1.3.5/docs/manual/index.html\">\n * Refer Dropwizard User Manual</a>\n */\npublic class JibExampleApplication extends Application<JibExampleConfiguration> {\n\n  public static void main(final String[] args) throws Exception {\n    new JibExampleApplication().run(args);\n  }\n\n  @Override\n  public String getName() {\n    return \"Dropwizard Jib Example\";\n  }\n\n  @Override\n  public void initialize(final Bootstrap<JibExampleConfiguration> bootstrap) {\n    // Enable FreeMarker config templates\n    bootstrap.addBundle(new TemplateConfigBundle());\n  }\n\n  @Override\n  public void run(final JibExampleConfiguration configuration, final Environment environment) {\n    final TemplateHealthCheck healthCheck =\n        new TemplateHealthCheck(configuration.getHelloConfiguration().getTemplate());\n    environment.healthChecks().register(\"template\", healthCheck);\n\n    environment.jersey().register(HelloWorldResource.from(configuration));\n  }\n}\n"
  },
  {
    "path": "examples/dropwizard/src/main/java/example/JibExampleConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport io.dropwizard.Configuration;\n\nimport example.config.HelloWorldConfiguration;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotNull;\n\n@SuppressWarnings(\"unused\")\npublic class JibExampleConfiguration extends Configuration {\n  @Valid @NotNull @JsonProperty\n  private HelloWorldConfiguration hello;\n\n  @JsonProperty(\"hello\")\n  public HelloWorldConfiguration getHelloConfiguration() {\n    return hello;\n  }\n}\n"
  },
  {
    "path": "examples/dropwizard/src/main/java/example/api/Saying.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example.api;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Saying {\n  private long id;\n\n  private String content;\n\n  public Saying() {\n    // Jackson deserialization\n  }\n\n  public Saying(long id, String content) {\n    this.id = id;\n    this.content = content;\n  }\n\n  @JsonProperty\n  public long getId() {\n    return id;\n  }\n\n  @JsonProperty\n  public String getContent() {\n    return content;\n  }\n}\n"
  },
  {
    "path": "examples/dropwizard/src/main/java/example/config/HelloWorldConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example.config;\n\nimport io.dropwizard.Configuration;\n\nimport org.hibernate.validator.constraints.NotEmpty;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n@SuppressWarnings(\"unused\")\npublic class HelloWorldConfiguration extends Configuration {\n\n  @NotEmpty private String template;\n\n  @NotEmpty private String defaultName = \"Stranger\";\n\n  @JsonProperty\n  public String getTemplate() {\n    return template;\n  }\n\n  @JsonProperty\n  public String getDefaultName() {\n    return defaultName;\n  }\n}\n"
  },
  {
    "path": "examples/dropwizard/src/main/java/example/health/TemplateHealthCheck.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example.health;\n\nimport com.codahale.metrics.health.HealthCheck;\n\npublic class TemplateHealthCheck extends HealthCheck {\n  private final String template;\n\n  public TemplateHealthCheck(String template) {\n    this.template = template;\n  }\n\n  @Override\n  protected Result check() throws Exception {\n    final String saying = String.format(template, \"TEST\");\n    if (!saying.contains(\"TEST\")) {\n      return Result.unhealthy(\"template doesn't include a name\");\n    }\n    return Result.healthy();\n  }\n}\n"
  },
  {
    "path": "examples/dropwizard/src/main/java/example/resources/HelloWorldResource.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example.resources;\n\nimport com.codahale.metrics.annotation.Timed;\n\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicLong;\n\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.QueryParam;\nimport javax.ws.rs.core.MediaType;\n\nimport example.JibExampleConfiguration;\nimport example.api.Saying;\nimport example.config.HelloWorldConfiguration;\n\n@Path(\"/\")\n@Produces(MediaType.APPLICATION_JSON)\npublic class HelloWorldResource {\n\n  private final String template;\n  private final String defaultName;\n  private final AtomicLong counter;\n\n  public HelloWorldResource(String template, String defaultName) {\n    this.template = template;\n    this.defaultName = defaultName;\n    this.counter = new AtomicLong();\n  }\n\n  public static HelloWorldResource from(JibExampleConfiguration conf) {\n    final HelloWorldConfiguration helloConfiguration = conf.getHelloConfiguration();\n    return new HelloWorldResource(\n        helloConfiguration.getTemplate(), helloConfiguration.getDefaultName());\n  }\n\n  @GET\n  @Timed\n  public Saying sayHello(@QueryParam(\"name\") Optional<String> name) {\n    final String value = String.format(template, name.orElse(defaultName));\n    return new Saying(counter.incrementAndGet(), value);\n  }\n}\n"
  },
  {
    "path": "examples/dropwizard/src/main/resources/banner.txt",
    "content": "================================================================================\n\n                              Dropwizard Jib Example\n\n================================================================================\n\n"
  },
  {
    "path": "examples/dropwizard/src/main/resources/dropwizard.yml",
    "content": "<#-- FreeMarker Enabled - https://github.com/tkrille/dropwizard-template-config -->\nhello:\n  template: ${DW_TEMPLATE!'Hello, %s!'}\n  defaultName: ${DW_DEFAULT_NAME!'Stranger'}\n\nserver:\n  type: simple\n  applicationContextPath: /\n  adminContextPath: /admin\n  connector:\n    type: http\n    port: 8080\n\nlogging:\n  level: ${LOG_LEVEL!'INFO'}\n  loggers:\n    <#-- Log level for a specific package -->\n    example: DEBUG\n"
  },
  {
    "path": "examples/helloworld/README.md",
    "content": "Builds a container image that outputs `Hello World` when run.\n\nTo build the image:\n\n1. In `pom.xml` or `build.gradle`, replace `REPLACE-WITH-YOUR-GCP-PROJECT` with your GCP project.\n\n1. Run `mvn compile jib:build` or `./gradlew jib`.\n"
  },
  {
    "path": "examples/helloworld/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib' version '3.5.3'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation 'com.google.guava:guava:23.6-jre'\n}\n\njib.to.image = 'gcr.io/REPLACE-WITH-YOUR-GCP-PROJECT/image-built-with-jib'\n"
  },
  {
    "path": "examples/helloworld/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/helloworld/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/helloworld/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/helloworld/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>example</groupId>\n  <artifactId>helloworld</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <jib-maven-plugin.version>3.5.1</jib-maven-plugin.version>\n    <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>32.0.0-jre</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>${maven-compiler-plugin.version}</version>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <!-- Jib -->\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <to>\n            <image>gcr.io/REPLACE-WITH-YOUR-GCP-PROJECT/image-built-with-jib</image>\n          </to>\n        </configuration>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>build</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "examples/helloworld/settings.gradle",
    "content": ""
  },
  {
    "path": "examples/helloworld/src/main/java/example/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport com.google.common.io.CharStreams;\n\nimport java.io.InputStreamReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\n\npublic class HelloWorld {\n\n  public static void main(String[] args) throws URISyntaxException, IOException {\n    try (Reader reader = new InputStreamReader(\n        HelloWorld.class.getResourceAsStream(\"/world\"), StandardCharsets.UTF_8)) {\n      String world = CharStreams.toString(reader);\n      System.out.println(\"Hello \" + world);\n    }\n  }\n}\n"
  },
  {
    "path": "examples/helloworld/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "examples/java-agent/README.md",
    "content": "This SparkJava-based example builds a container image that includes the [Stackdriver Debugger Java Agent](https://cloud.google.com/debugger/docs/).\nThis project assumes the resulting image will be run inside Google Cloud Platform.\n\nTo build the image:\n\n1. Replace `REPLACE-WITH-YOUR-GCP-PROJECT` with your GCP project in `pom.xml` or `build.gradle`.\n\n1. Run `mvn package` or `./gradlew` to build the image.\n\nSparkJava listens on port 4567 by default.\n\n## Build and run on Google Cloud\n\n[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run?git_repo=https://github.com/GoogleContainerTools/jib.git&dir=examples/java-agent)\n"
  },
  {
    "path": "examples/java-agent/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib' version '3.5.3'\n  id 'de.undercouch.download' version '4.0.0'\n  id 'com.gorylenko.gradle-git-properties' version '2.2.0'\n}\n\next {\n  // where to download the Stackdriver Debugger agent https://cloud.google.com/debugger/docs/setup/java\n  stackdriverDebuggerAgentUrl = 'https://storage.googleapis.com/cloud-debugger/compute-java/debian-wheezy/cdbg_java_agent_gce.tar.gz'\n\n  // where to place the Cloud Debugger agent in the container\n  stackdriverDebuggerLocation = '/opt/cdbg'\n\n  // location for jib extras, including the Java agent\n  jibExtraDirectory = \"${buildDir}/jib-agents\"\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation 'com.sparkjava:spark-core:2.9.1'\n  implementation 'org.slf4j:slf4j-simple:1.7.28'\n}\n\n// Download and extract the Cloud Debugger Java Agent\ntask downloadAgent(type: Download) {\n  src stackdriverDebuggerAgentUrl\n  dest \"${buildDir}/cdbg_java_agent_gce.tar.gz\"\n}\ntask extractAgent(dependsOn: downloadAgent, type: Copy) {\n  from tarTree(downloadAgent.dest)\n  into \"${jibExtraDirectory}/${stackdriverDebuggerLocation}\"\n}\n\njib {\n  to {\n    image = 'gcr.io/REPLACE-WITH-YOUR-GCP-PROJECT/image-built-with-jib'\n  }\n  extraDirectories.paths = [file(jibExtraDirectory)]\n  container {\n    ports = ['4567']\n    jvmFlags = [\n        '-agentpath:' + stackdriverDebuggerLocation + '/cdbg_java_agent.so=--logtostderr=1',\n        '-Dcom.google.cdbg.module=' + project.name,\n        '-Dcom.google.cdbg.version=' + version]\n  }\n}\n\ntasks.jib.dependsOn extractAgent\ntasks.jibDockerBuild.dependsOn extractAgent\ntasks.jibBuildTar.dependsOn extractAgent\ndefaultTasks 'jib'\n"
  },
  {
    "path": "examples/java-agent/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/java-agent/gradle.properties",
    "content": "version = 0.0.1-SNAPSHOT\n"
  },
  {
    "path": "examples/java-agent/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/java-agent/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/java-agent/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>example</groupId>\n  <artifactId>java-agent</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <jib-maven-plugin.version>3.5.1</jib-maven-plugin.version>\n    <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>\n    <download-maven-plugin.version>1.4.2</download-maven-plugin.version>\n    <git-commit-id-plugin.version>3.0.1</git-commit-id-plugin.version>\n\n    <!-- agents will be extracted relative to this build location -->\n    <agent-extraction-root>${project.build.directory}/jib-agents</agent-extraction-root>\n\n    <!-- location of Stackdriver Debugger Java Agent https://cloud.google.com/debugger/docs/setup/java -->\n    <stackdriver-debugger-agent-url>https://storage.googleapis.com/cloud-debugger/compute-java/debian-wheezy/cdbg_java_agent_gce.tar.gz</stackdriver-debugger-agent-url>\n\n    <!-- where to place the Stackdriver Debugger Java Agent in the container -->\n    <stackdriver-debugger-agent-location>/opt/cdbg</stackdriver-debugger-agent-location>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.sparkjava</groupId>\n      <artifactId>spark-core</artifactId>\n      <version>2.9.1</version>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-simple</artifactId>\n      <version>1.7.28</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>${maven-compiler-plugin.version}</version>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>pl.project13.maven</groupId>\n        <artifactId>git-commit-id-plugin</artifactId>\n        <version>${git-commit-id-plugin.version}</version>\n        <executions>\n          <execution>\n            <goals>\n              <goal>revision</goal>\n            </goals>\n            <!-- *NOTE*: The default phase of revision is initialize, but \n              in case you want to change it, you can do so by adding the phase here -->\n            <phase>initialize</phase>\n            <configuration>\n              <generateGitPropertiesFile>true</generateGitPropertiesFile>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n\n      <!-- Jib -->\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <to>\n            <image>gcr.io/REPLACE-WITH-YOUR-GCP-PROJECT/image-built-with-jib</image>\n          </to>\n          <extraDirectories>\n            <paths>${agent-extraction-root}</paths>\n          </extraDirectories>\n          <container>\n            <ports>4567</ports>\n            <jvmFlags>\n              <jvmFlag>-agentpath:${stackdriver-debugger-agent-location}/cdbg_java_agent.so=--logtostderr=1</jvmFlag>\n              <jvmFlag>-Dcom.google.cdbg.module=${project.artifactId}</jvmFlag>\n              <jvmFlag>-Dcom.google.cdbg.version=${project.version}</jvmFlag>\n            </jvmFlags>\n          </container>\n        </configuration>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>build</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n\n      <plugin>\n        <groupId>com.googlecode.maven-download-plugin</groupId>\n        <artifactId>download-maven-plugin</artifactId>\n        <version>${download-maven-plugin.version}</version>\n        <executions>\n          <execution>\n            <id>install-stackdriver-debugger</id>\n            <phase>prepare-package</phase>\n            <goals>\n              <goal>wget</goal>\n            </goals>\n            <configuration>\n              <url>${stackdriver-debugger-agent-url}</url>\n              <unpack>true</unpack>\n              <outputDirectory>${agent-extraction-root}/${stackdriver-debugger-agent-location}</outputDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "examples/java-agent/settings.gradle",
    "content": ""
  },
  {
    "path": "examples/java-agent/src/main/java/example/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport static spark.Spark.get;\nimport static spark.Spark.port;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    // Allow use with Cloud Run which requires listening on the value in PORT\n    String portEnv = System.getenv(\"PORT\");\n    if (portEnv != null) {\n      port(Integer.parseInt(portEnv));\n    }\n\n    get(\"/\", (req, res) -> \"Hello World\");\n  }\n}\n"
  },
  {
    "path": "examples/java-agent/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "examples/ktor/README.md",
    "content": "# Containerize a [Ktor] application with Jib\n\nThis is an example of how to easily build a Docker image for a [Ktor] application with Jib.\n\n```shell\n./gradlew jibDockerBuild\n\ndocker run --rm -p 8080:8080 ktor-jib-example:1\n```\n\nThe application can also be ran outside of Jib's build process via `./gradlew run`\n\n## Defined environment variables\n\nA few variables have been added in the code-base to show case some of the unique features of Ktor.\n\n- `KTOR_APP_ID` - The name of the application in the logs\n    - Type: String\n- `KTOR_METRICS_ENABLED` - Exposes JMX Metrics through the application port (`8080`)\n    - Type: Boolean (default: `false`)\n- `KTOR_ROUTE_TRACING` - Enables verbose logging of route matches for debugging complex/nested routing tables\n    - Type: Boolean\n\n## More information\n\nLearn [more about Jib](https://github.com/GoogleContainerTools/jib).\n\nLearn [Ktor].\n\n  [Ktor]: https://ktor.io\n"
  },
  {
    "path": "examples/ktor/build.gradle.kts",
    "content": "plugins {\n    application\n    kotlin(\"jvm\") version \"1.3.10\"\n    id(\"com.google.cloud.tools.jib\") version \"3.5.3\"\n}\n\ngroup = \"example\"\nversion = \"1\"\n\nval ktor_version by extra(\"1.0.0\")\nval logback_version by extra(\"1.2.3\")\n\nval main_class by extra(\"io.ktor.server.netty.EngineMain\")\n\napplication {\n    mainClassName = main_class\n\n    applicationDefaultJvmArgs = listOf(\n            \"-server\",\n            \"-Djava.awt.headless=true\",\n            \"-Xms128m\",\n            \"-Xmx256m\",\n            \"-XX:+UseG1GC\",\n            \"-XX:MaxGCPauseMillis=100\"\n    )\n}\n\njava.sourceCompatibility = JavaVersion.VERSION_1_8\n\ndependencies {\n    implementation(kotlin(\"stdlib\"))\n\n    implementation(\"ch.qos.logback:logback-classic:$logback_version\")\n\n    implementation(\"io.ktor:ktor-server-core:$ktor_version\")\n    implementation(\"io.ktor:ktor-server-netty:$ktor_version\")\n    implementation(\"io.ktor:ktor-metrics:$ktor_version\")\n    implementation(\"io.ktor:ktor-html-builder:$ktor_version\")\n}\n\njib {\n    container {\n        ports = listOf(\"8080\")\n        mainClass = main_class\n\n        // good defauls intended for Java 8 (>= 8u191) containers\n        jvmFlags = listOf(\n                \"-server\",\n                \"-Djava.awt.headless=true\",\n                \"-XX:InitialRAMFraction=2\",\n                \"-XX:MinRAMFraction=2\",\n                \"-XX:MaxRAMFraction=2\",\n                \"-XX:+UseG1GC\",\n                \"-XX:MaxGCPauseMillis=100\",\n                \"-XX:+UseStringDeduplication\"\n        )\n    }\n}\n\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "examples/ktor/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/ktor/gradle.properties",
    "content": "kotlin.code.style=official"
  },
  {
    "path": "examples/ktor/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/ktor/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/ktor/settings.gradle.kts",
    "content": "rootProject.name = \"ktor-jib-example\"\n"
  },
  {
    "path": "examples/ktor/src/main/kotlin/example/ktor/App.kt",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example.ktor\n\nimport com.codahale.metrics.JmxReporter\nimport io.ktor.application.*\nimport io.ktor.features.CallLogging\nimport io.ktor.features.DefaultHeaders\nimport io.ktor.features.StatusPages\nimport io.ktor.html.respondHtml\nimport io.ktor.http.ContentType\nimport io.ktor.http.HttpStatusCode\nimport io.ktor.metrics.Metrics\nimport io.ktor.request.receiveOrNull\nimport io.ktor.response.respond\nimport io.ktor.response.respondText\nimport io.ktor.routing.get\nimport io.ktor.routing.post\nimport io.ktor.routing.route\nimport io.ktor.routing.routing\nimport kotlinx.html.*\nimport java.util.concurrent.TimeUnit\n\n/**\n * Ktor Example Application\n *\n * See documentation at https://ktor.io/servers/index.html\n */\nfun Application.main() {\n    init()\n    router()\n}\n\n// Variable for maintaining health-check status\nvar healthy = true\n\n/**\n * For enabling Metrics - https://ktor.io/servers/features/metrics.html\n */\nval metricsEnabled = (true == System.getenv(\"KTOR_METRICS_ENABLED\")?.toBoolean())\n\n/**\n * For enabling route tracing - https://ktor.io/servers/features/routing.html#tracing\n */\nval routeTracing = (true == System.getenv(\"KTOR_ROUTE_TRACING\")?.toBoolean())\n\n/**\n * A method for defining the routes - https://ktor.io/servers/structure.html#extracting-routes\n */\nprivate fun Application.router() {\n    log.info(\"Route tracing enabled? $routeTracing\")\n    routing {\n        // If enabled, will print out a tree of the routes that were matched while trying to process the URI\n        if (routeTracing) {\n            trace { application.log.trace(it.buildText()) }\n        }\n\n        get(\"/\") {\n            call.respondText(\"Hello World!\")\n        }\n\n        get(\"/info\") {\n            // The `respondHtml` extension method is available at the `ktor-html-builder` artifact.\n            // It provides a DSL for building HTML to a Writer, potentially in a chunked way.\n            // More information about this DSL: https://ktor.io/servers/features/templates/html-dsl.html\n            call.respondHtml {\n                head {\n                    title { +\"Ktor: Jib\" }\n                }\n                body {\n                    val runtime = Runtime.getRuntime()\n                    h1 { +\"Hello from Ktor sample application built with Jib\" }\n                    val runtimeString = \"Runtime.getRuntime()\"\n                    p { +\"$runtimeString.availableProcessors(): ${runtime.availableProcessors()}\" }\n                    p { +\"$runtimeString.freeMemory(): ${runtime.freeMemory()}\" }\n                    p { +\"$runtimeString.totalMemory(): ${runtime.totalMemory()}\" }\n                    p { +\"$runtimeString.maxMemory(): ${runtime.maxMemory()}\" }\n                    p { +\"System.getProperty(\\\"user.name\\\"): ${System.getProperty(\"user.name\")}\" }\n                }\n            }\n        }\n\n        route(\"/healthz\") {\n            // Some simple toy health check example\n            post(\"/\") {\n                when(call.receiveOrNull<String>()) {\n                    \"infect\" -> {\n                        healthy = false\n                        call.respond(HttpStatusCode.Accepted)\n                    }\n                    \"heal\" -> {\n                        healthy = true\n                        call.respond(HttpStatusCode.Accepted)\n                    }\n                    else -> {\n                        // Showcase the StatusPage installation feature\n                        throw IllegalAccessException(\"POST /healthz only accepts 'infect' or 'heal'\")\n                    }\n                }\n            }\n            get(\"/\") {\n                var txt = \"ok\"\n                var status = HttpStatusCode.OK\n                if (!healthy) {\n                    txt = \"failure\"\n                    status = HttpStatusCode.ServiceUnavailable\n                }\n                call.respondText(txt.toUpperCase(), status = status)\n            }\n        }\n    }\n}\n\n/**\n * A method for installing features - https://ktor.io/servers/features.html#installing\n */\nprivate fun Application.init() {\n    // This adds automatically Date and Server headers to each response, and would allow you to configure\n    // additional headers served to each response.\n    install(DefaultHeaders)\n\n    // This allows handling exceptions and status codes in a specific way.\n    install(StatusPages) {\n        // For all thrown Exceptions, log, and return the message of it as text\n        exception<Throwable> { e ->\n            val message = e.localizedMessage\n            application.log.error(message)\n            call.respondText(message,\n                    ContentType.Text.Plain, HttpStatusCode.InternalServerError)\n        }\n    }\n\n    // This uses use the logger to log every call (request/response)\n    install(CallLogging)\n\n    // Conditionally enable the Metrics API\n    log.info(\"Metrics Registry enabled? $metricsEnabled\")\n    if (metricsEnabled) {\n        install(Metrics) {\n            JmxReporter.forRegistry(registry)\n                    .convertRatesTo(TimeUnit.SECONDS)\n                    .convertDurationsTo(TimeUnit.MILLISECONDS)\n                    .build()\n                    .start()\n        }\n    }\n}"
  },
  {
    "path": "examples/ktor/src/main/resources/application.conf",
    "content": "# Ktor configuration - https://ktor.io/servers/configuration.html\nktor {\n    deployment {\n        port = 8080\n    }\n\n    application {\n        # Defining values twice is way to define a default with substitutable value\n        id = \"Ktor Jib Example\"\n        id = ${?KTOR_APP_ID}\n        modules = [ example.ktor.AppKt.main ]\n    }\n}"
  },
  {
    "path": "examples/ktor/src/main/resources/logback.xml",
    "content": "<!-- https://ktor.io/servers/logging.html -->\n<configuration>\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"debug\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.eclipse.jetty\" level=\"INFO\"/>\n    <logger name=\"io.netty\" level=\"INFO\"/>\n</configuration>"
  },
  {
    "path": "examples/micronaut/README.md",
    "content": "# Containerize a [Micronaut](http://micronaut.io/) app with Jib\n\nThis is an example of how to easily build a Docker image for a [Micronaut framework Groovy/Java application](http://guides.micronaut.io/creating-your-first-micronaut-app-groovy/guide/index.html) with Jib.\n\n<!-- Dockerize and run a \"Hello World\" @Java @micronautfw app with #Jib in seconds -->\n<p align=\"center\">\n    <a href=\"https://twitter.com/intent/tweet?text=Dockerize%20and%20run%20a%20%22Hello%20World%22%20%40Java%20%40micronautfw%20app%20with%20%23Jib%20in%20seconds&url=https://asciinema.org/a/191805&hashtags=docker,kubernetes\">\n    <img src=\"dockerize-micronaut-jib.gif?raw=true\" width=\"600\" alt=\"Dockerize Micronaut app with Jib\">\n  </a>\n</p>\n\n## Quickstart\n\n### With Docker\n\n```shell\n./gradlew jibDockerBuild\n\ndocker run -d -p 8080:8080 micronaut-jib:0.1\n```\n```shell\ncurl localhost:8080/hello\n> Hello World\n```\n\n<!-- Dockerize and run a \"Hello World\" @Java @micronautfw app with #Jib in seconds -->\nGive it a [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Dockerize%20and%20run%20a%20%22Hello%20World%22%20%40Java%20%40micronautfw%20app%20with%20%23Jib%20in%20seconds&url=https://github.com/GoogleContainerTools/jib/tree/master/examples/micronaut&hashtags=docker,kubernetes)\n\n### With Kubernetes\n\n```shell\nIMAGE=<your image, eg. gcr.io/my-project/micronaut-jib>\n\n./gradlew jib --image=$IMAGE\n\nkubectl run micronaut-jib --image=$IMAGE --port=8080 --restart=Never\n\n# Wait until pod is running\nkubectl port-forward micronaut-jib 8080 > /dev/null 2>&1 &\n```\n```shell\ncurl localhost:8080/hello\n> Hello World\n```\n\n<!-- Run a \"Hello World\" @java @micronautfw app on #Kubernetes with #Jib in seconds -->\nGive it a [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Run%20a%20%22Hello%20World%22%20%40java%20%40micronautfw%20app%20on%20%23Kubernetes%20with%20%23Jib%20in%20seconds&url=https://github.com/GoogleContainerTools/jib/tree/master/examples/micronaut&hashtags=docker,kubernetes)\n\n## More information\n\nLearn [more about Jib](https://github.com/GoogleContainerTools/jib).\nLearn [more about Micronaut](https://micronaut.io).\n"
  },
  {
    "path": "examples/micronaut/build.gradle",
    "content": "plugins {\n    id \"groovy\"\n    id \"com.github.johnrengelman.shadow\" version \"5.2.0\"\n    id \"application\"\n    id 'com.google.cloud.tools.jib' version '3.5.3'\n}\n\nversion \"0.1\"\ngroup 'example.micronaut-jib'\n\nrepositories {\n    mavenCentral()\n}\n\nconfigurations {\n    // for dependencies that are needed for development only\n    developmentOnly\n}\n\ndependencies {\n    compileOnly(enforcedPlatform(\"io.micronaut:micronaut-bom:$micronautVersion\"))\n    compileOnly(\"io.micronaut:micronaut-inject-groovy\")\n    implementation(enforcedPlatform(\"io.micronaut:micronaut-bom:$micronautVersion\"))\n    implementation(\"io.micronaut:micronaut-inject\")\n    implementation(\"io.micronaut:micronaut-validation\")\n    implementation(\"io.micronaut:micronaut-runtime-groovy\")\n    implementation(\"javax.annotation:javax.annotation-api\")\n    implementation(\"io.micronaut:micronaut-http-server-netty\")\n    implementation(\"io.micronaut:micronaut-http-client\")\n    runtimeOnly(\"ch.qos.logback:logback-classic:1.2.3\")\n    testImplementation enforcedPlatform(\"io.micronaut:micronaut-bom:$micronautVersion\")\n    testImplementation(\"io.micronaut:micronaut-inject-groovy\")\n    testImplementation(\"org.spockframework:spock-core\") {\n        exclude group: \"org.codehaus.groovy\", module: \"groovy-all\"\n    }\n    testImplementation(\"io.micronaut.test:micronaut-test-spock\")\n}\n\ntest.classpath += configurations.developmentOnly\n\nmainClassName = \"example.micronaut.Application\"\n\ntasks.withType(GroovyCompile) {\n    groovyOptions.forkOptions.jvmArgs.add('-Dgroovy.parameters=true')\n}\n\nshadowJar {\n    mergeServiceFiles()\n}\n\ntasks.withType(JavaExec) {\n    classpath += configurations.developmentOnly\n    jvmArgs('-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote')\n}\n"
  },
  {
    "path": "examples/micronaut/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/micronaut/gradle.properties",
    "content": "micronautVersion=2.0.0.M3"
  },
  {
    "path": "examples/micronaut/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/micronaut/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/micronaut/settings.gradle",
    "content": "rootProject.name = 'micronaut-jib'\n"
  },
  {
    "path": "examples/micronaut/src/main/groovy/example/micronaut/Application.groovy",
    "content": "package example.micronaut\n\nimport groovy.transform.CompileStatic\nimport io.micronaut.runtime.Micronaut\n\n@CompileStatic\nclass Application {\n\n    static void main(String[] args) {\n        Micronaut.run(Application.class)\n    }\n}"
  },
  {
    "path": "examples/micronaut/src/main/groovy/example/micronaut/HelloController.groovy",
    "content": "package example.micronaut\n\nimport groovy.transform.CompileStatic\nimport io.micronaut.http.MediaType\nimport io.micronaut.http.annotation.Controller\nimport io.micronaut.http.annotation.Get\nimport io.micronaut.http.annotation.Produces\n\n@CompileStatic\n@Controller(\"/hello\") // <1>\nclass HelloController {\n\n    @Produces(MediaType.TEXT_PLAIN)\n    @Get(\"/\") // <2>\n    String index() {\n        \"Hello World\" // <3>\n    }\n}\n"
  },
  {
    "path": "examples/micronaut/src/main/resources/application.yml",
    "content": "micronaut:\n    application:\n        name: examples"
  },
  {
    "path": "examples/micronaut/src/main/resources/logback.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"STDOUT\" />\n    </root>\n</configuration>"
  },
  {
    "path": "examples/micronaut/src/test/groovy/example/micronaut/HelloControllerSpec.groovy",
    "content": "package example.micronaut\n\nimport io.micronaut.context.ApplicationContext\nimport io.micronaut.http.HttpRequest\nimport io.micronaut.http.client.RxHttpClient\nimport io.micronaut.runtime.server.EmbeddedServer\nimport spock.lang.AutoCleanup\nimport spock.lang.Shared\nimport spock.lang.Specification\n\nclass HelloControllerSpec extends Specification {\n\n    @Shared\n    @AutoCleanup // <1>\n    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer) // <2>\n\n    @Shared\n    @AutoCleanup\n    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) // <3>\n\n    void \"test hello world response\"() {\n        when:\n        HttpRequest request = HttpRequest.GET('/hello') // <4>\n        String rsp  = client.toBlocking().retrieve(request)\n\n        then:\n        rsp == \"Hello World\"\n    }\n\n}"
  },
  {
    "path": "examples/multi-module/.mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\nLicensed to the Apache Software Foundation (ASF) under one\nor more contributor license agreements.  See the NOTICE file\ndistributed with this work for additional information\nregarding copyright ownership.  The ASF licenses this file\nto you under the Apache License, Version 2.0 (the\n\"License\"); you may not use this file except in compliance\nwith the License.  You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing,\nsoftware distributed under the License is distributed on an\n\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, either express or implied.  See the License for the\nspecific language governing permissions and limitations\nunder the License.\n*/\n\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL =\n            \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: : \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output direcrory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": "examples/multi-module/.mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip"
  },
  {
    "path": "examples/multi-module/README.md",
    "content": "# Multi-module example\n\nThis example shows how to build multiple containers for a multi-module project in both **Maven** and **Gradle**.\n\n# How the example is set up\n\nThe project consists of two microservices and a library:\n\n1. `name-service` - responds with a name\n1. `shared-library` - a project dependency used by `name-service`\n1. `hello-service` - calls `name-service` and responds with a greeting\n\nThe **Maven** project is set up with a parent POM ([`pom.xml`](pom.xml)) that defines most of the common build configuration. The module POMs ([`name-service/pom.xml`](name-service/pom.xml) and [`hello-service/pom.xml`](hello-service/pom.xml)) just define inheritance on the parent POM. However, if needed, the module POMs can define custom configuration on `jib-maven-plugin` specific to that module.\n\nThe **Gradle** project is set up with a parent [`build.gradle`](build.gradle) that sets some common configuration up for all projects, with each sub-project containing its own `build.gradle` with some custom configuration. [`settings.gradle`](settings.gradle) defines which modules to include in the overall build.\n\n## Reproducibility of dependency module `shared-library`\n\nSince dependency module builds happen with the underlying build system\n(maven/gradle), we must add some extra configuration to ensure that the\nresulting `jar` that is built conforms to our reproducibility expectations.\nThe module [`shared-library`](shared-library) uses the [Reproducible Build Maven Plugin](https://zlika.github.io/reproducible-build-maven-plugin/)\nfor maven, and some special `Jar` properties ([`preserveFileTimestamps`](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html#org.gradle.api.tasks.bundling.Jar:preserveFileTimestamps),\n[`reproducibleFileOrder`](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html#org.gradle.api.tasks.bundling.Jar:reproducibleFileOrder))\nin gradle to achieve this. This configuration can be seen in the\n`shared-library`'s [`pom.xml`](shared-library/pom.xml) and [`build.gradle`](shared-library/build.gradle).\n\nCare must be taken when adding custom attributes to a `MANIFEST.MF`.\nAttributes whose values change on every build can affect reproducibility even\nwith the modifications outlined above.\n\n# How to run\n\nSet the `PROJECT_ID` environment variable to your own Google Cloud Platform project:\n\n```shell\nexport PROJECT_ID=$(gcloud config list --format 'value(core.project)')\n```\n\nRun the **Maven** build:\n\n```shell\n# build everything\n./mvnw package jib:build\n\n# build just hello-service\n./mvnw compile jib:build -pl hello-service\n\n# build name-service (with dependency on shared-library)\n# you must use \"package\" for jib to correctly package \"shared-library\" with the\n# \"name-service\" container\n./mvnw package jib:build -pl name-service -am\n```\n\nRun the **Gradle** build:\n\n```shell\n# build everything\n./gradlew jib\n\n# build just hello-service\n./gradlew :hello-service:jib\n\n# build name-service (with dependency on shared-library)\n./gradlew :name-service:jib\n```\n\nYou can also run `./maven-build.sh` or `./gradle-build.sh` as a shorthand.\n\n# Where are the containers\n\nThe output of the build should have the container image references highlighted in <span style=\"color: cyan\">cyan</span>. You can expect them to be at:\n\n- `name-service`: `gcr.io/PROJECT_ID/name-service:0.1.0`\n- `hello-service`: `gcr.io/PROJECT_ID/hello-service:0.1.0`\n\n# How to run on Kubernetes\n\n[`kubernetes.yaml`](kubernetes.yaml) defines the manifests for running the two microservices on Kubernetes. Make sure to open the file and change `PROJECT_ID` to your own Google Cloud Platform project.\n\nCreate a Kubernetes cluster:\n\n```shell\ngcloud container clusters create jib\n```\n\nApply to your Kubernetes cluster:\n\n```shell\nkubectl apply -f kubernetes.yaml\n```\n\nFind the `EXTERNAL-IP` of the `hello-service`.\n\n```\nNAME                TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE\nsvc/hello-service   LoadBalancer   10.19.243.223   35.237.89.148   80:30196/TCP   1m\n```\n\nVisit the IP in your web browser and you should see:\n\n```\nHello Jib Multimodule: A string from 'shared-library'\n```\n"
  },
  {
    "path": "examples/multi-module/build.gradle",
    "content": "// Define plugin versions here, but only apply them where we need to\nplugins {\n  id 'org.springframework.boot' version '2.0.3.RELEASE' apply false\n  id 'io.spring.dependency-management' version '1.0.6.RELEASE' apply false\n  id 'com.google.cloud.tools.jib' version '3.5.3' apply false\n}\n"
  },
  {
    "path": "examples/multi-module/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/multi-module/gradle-build.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nexport PROJECT_ID=$(gcloud config list --format 'value(core.project)')\n./gradlew jib\n"
  },
  {
    "path": "examples/multi-module/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/multi-module/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/multi-module/hello-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'eclipse'\n  id 'idea'\n  id 'org.springframework.boot'\n  id 'io.spring.dependency-management'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation 'org.springframework.boot:spring-boot-starter-web'\n}\n\n// IMPORTANT: Set the environment variable PROJECT_ID to your own Google Cloud Platform project.\njib.to.image = \"gcr.io/${System.getenv('PROJECT_ID')}/${project.name}:${version}\"\n"
  },
  {
    "path": "examples/multi-module/hello-service/gradle.properties",
    "content": "version = 0.1.0\n"
  },
  {
    "path": "examples/multi-module/hello-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this module. -->\n  <artifactId>hello-service</artifactId>\n  <version>0.1.0</version>\n\n  <!-- Inherits from the Jib Multimodule parent POM. -->\n  <parent>\n    <groupId>com.example</groupId>\n    <artifactId>jib-multimodule</artifactId>\n    <version>0.1.0</version>\n  </parent>\n</project>\n"
  },
  {
    "path": "examples/multi-module/hello-service/src/main/java/hello/Application.java",
    "content": "package hello;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}\n"
  },
  {
    "path": "examples/multi-module/hello-service/src/main/java/hello/HelloController.java",
    "content": "package hello;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\n@RestController\npublic class HelloController {\n    \n  @RequestMapping(\"/\")\n  public String sayHello() {\n    String name = new RestTemplate().getForEntity(\"http://name-service\", String.class).getBody();\n    return \"Hello \" + name;\n  }\n}\n"
  },
  {
    "path": "examples/multi-module/kubernetes.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: name-service\n  name: name-service\nspec:\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: name-service\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: name-service\n  name: name-service\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: name-service\n  template:\n    metadata:\n      labels:\n        app: name-service\n    spec:\n      containers:\n      - name: name-service\n        image: gcr.io/PROJECT_ID/name-service:0.1.0\n        ports:\n        - name: http\n          containerPort: 8080\n        imagePullPolicy: Always\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app: hello-service\n  name: hello-service\nspec:\n  type: LoadBalancer\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: hello-service\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: hello-service\n  name: hello-service\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: hello-service\n  template:\n    metadata:\n      labels:\n        app: hello-service\n    spec:\n      containers:\n      - name: hello-service\n        image: gcr.io/PROJECT_ID/hello-service:0.1.0\n        ports:\n        - name: http\n          containerPort: 8080\n        imagePullPolicy: Always\n"
  },
  {
    "path": "examples/multi-module/maven-build.sh",
    "content": "#!/bin/sh\n\nset -ex\n\nexport PROJECT_ID=$(gcloud config list --format 'value(core.project)')\n# if there are no intermodule dependencies, compile is enough to complete a jib build.\n./mvnw compile jib:build -pl hello-service\n\n# multi module builds with dependencies on other modules in the build require that the\n# \"package\" phase is executed as part of the build to correctly include module dependencies.\n./mvnw package jib:build -pl name-service -am\n"
  },
  {
    "path": "examples/multi-module/mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar\"\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        wget \"$jarUrl\" -O \"$wrapperJarPath\"\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        curl -o \"$wrapperJarPath\" \"$jarUrl\"\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "examples/multi-module/mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Maven2 Start Up Batch script\r\n@REM\r\n@REM Required ENV vars:\r\n@REM JAVA_HOME - location of a JDK home dir\r\n@REM\r\n@REM Optional ENV vars\r\n@REM M2_HOME - location of maven2's installed home dir\r\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\r\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r\n@REM     e.g. to debug Maven itself, use\r\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r\n@echo off\r\n@REM set title of command window\r\ntitle %0\r\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\r\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\r\n\r\n@REM set %HOME% to equivalent of $HOME\r\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\r\n\r\n@REM Execute a user defined script before this one\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\r\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\r\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\r\n:skipRcPre\r\n\r\n@setlocal\r\n\r\nset ERROR_CODE=0\r\n\r\n@REM To isolate internal variables from possible post scripts, we use another setlocal\r\n@setlocal\r\n\r\n@REM ==== START VALIDATION ====\r\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\r\n\r\necho.\r\necho Error: JAVA_HOME not found in your environment. >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n:OkJHome\r\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\r\n\r\necho.\r\necho Error: JAVA_HOME is set to an invalid directory. >&2\r\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n@REM ==== END VALIDATION ====\r\n\r\n:init\r\n\r\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\r\n@REM Fallback to current working directory if not found.\r\n\r\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\r\n\r\nset EXEC_DIR=%CD%\r\nset WDIR=%EXEC_DIR%\r\n:findBaseDir\r\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\r\ncd ..\r\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\r\nset WDIR=%CD%\r\ngoto findBaseDir\r\n\r\n:baseDirFound\r\nset MAVEN_PROJECTBASEDIR=%WDIR%\r\ncd \"%EXEC_DIR%\"\r\ngoto endDetectBaseDir\r\n\r\n:baseDirNotFound\r\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r\ncd \"%EXEC_DIR%\"\r\n\r\n:endDetectBaseDir\r\n\r\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\r\n\r\n@setlocal EnableExtensions EnableDelayedExpansion\r\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r\n\r\n:endReadAdditionalConfig\r\n\r\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\r\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\r\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r\n\r\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar\"\r\nFOR /F \"tokens=1,2 delims==\" %%A IN (%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties) DO (\r\n\tIF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B \r\n)\r\n\r\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\r\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\r\nif exist %WRAPPER_JAR% (\r\n    echo Found %WRAPPER_JAR%\r\n) else (\r\n    echo Couldn't find %WRAPPER_JAR%, downloading it ...\r\n\techo Downloading from: %DOWNLOAD_URL%\r\n    powershell -Command \"(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"\r\n    echo Finished downloading %WRAPPER_JAR%\r\n)\r\n@REM End of extension\r\n\r\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r\nif ERRORLEVEL 1 goto error\r\ngoto end\r\n\r\n:error\r\nset ERROR_CODE=1\r\n\r\n:end\r\n@endlocal & set ERROR_CODE=%ERROR_CODE%\r\n\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\r\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\r\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\r\n:skipRcPost\r\n\r\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\r\n\r\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\r\n\r\nexit /B %ERROR_CODE%\r\n"
  },
  {
    "path": "examples/multi-module/name-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'eclipse'\n  id 'idea'\n  id 'org.springframework.boot'\n  id 'io.spring.dependency-management'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation 'org.springframework.boot:spring-boot-starter-web'\n  implementation project(':shared-library')\n}\n\n// IMPORTANT: Set the environment variable PROJECT_ID to your own Google Cloud Platform project.\njib.to.image = \"gcr.io/${System.getenv('PROJECT_ID')}/${project.name}:${version}\"\n"
  },
  {
    "path": "examples/multi-module/name-service/gradle.properties",
    "content": "version = 0.1.0\n"
  },
  {
    "path": "examples/multi-module/name-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this module. -->\n  <artifactId>name-service</artifactId>\n  <version>0.1.0</version>\n\n  <!-- Inherits from the Jib Multimodule parent POM. -->\n  <parent>\n    <groupId>com.example</groupId>\n    <artifactId>jib-multimodule</artifactId>\n    <version>0.1.0</version>\n  </parent>\n\n  <dependencies>\n    <dependency>\n      <!-- a module dependency on \"shared-library\" -->\n      <groupId>com.example</groupId>\n      <artifactId>shared-library</artifactId>\n      <version>0.1.0</version>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "examples/multi-module/name-service/src/main/java/name/Application.java",
    "content": "package name;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}\n"
  },
  {
    "path": "examples/multi-module/name-service/src/main/java/name/NameController.java",
    "content": "package name;\n\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport common.SharedUtils;\n\n@RestController\npublic class NameController {\n\n  @RequestMapping(\"/\")\n  public String getText() {\n    return \"Jib Multimodule: \" + SharedUtils.getText();\n  }\n}\n"
  },
  {
    "path": "examples/multi-module/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n                             http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this parent POM. -->\n  <groupId>com.example</groupId>\n  <artifactId>jib-multimodule</artifactId>\n  <packaging>pom</packaging>\n  <version>0.1.0</version>\n  <name>Jib Multi-module Example</name>\n\n  <!-- Inherits from the Spring Boot Starter POM. -->\n  <parent>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-parent</artifactId>\n    <version>2.0.3.RELEASE</version>\n  </parent>\n\n  <!-- The modules that are part of this project. -->\n  <modules>\n    <module>name-service</module>\n    <module>hello-service</module>\n    <module>shared-library</module>\n  </modules>\n\n  <!-- Uses Java 8. -->\n  <properties>\n    <java.version>1.8</java.version>\n\n    <!-- IMPORTANT: Set the environment variable PROJECT_ID to your own Google Cloud Platform project. -->\n    <image>gcr.io/${env.PROJECT_ID}/${project.artifactId}:${project.version}</image>\n  </properties>\n\n  <build>\n    <!-- Defines plugins that are used in the modules. -->\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.google.cloud.tools</groupId>\n          <artifactId>jib-maven-plugin</artifactId>\n          <version>3.5.1</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n\n  <!-- Defines dependencies common to all modules. -->\n  <dependencies>\n    <dependency>\n      <groupId>org.springframework.boot</groupId>\n      <artifactId>spring-boot-starter-web</artifactId>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "examples/multi-module/settings.gradle",
    "content": "rootProject.name = 'jib-multimodule'\ninclude 'name-service', 'hello-service', 'shared-library'\n"
  },
  {
    "path": "examples/multi-module/shared-library/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'eclipse'\n  id 'idea'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\n// Since this library is included as a jar in our jib projects, we want the\n// jar to built reproducibly.\njar {\n  preserveFileTimestamps false\n  reproducibleFileOrder true\n}\n"
  },
  {
    "path": "examples/multi-module/shared-library/gradle.properties",
    "content": "version = 0.1.0\n"
  },
  {
    "path": "examples/multi-module/shared-library/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this module. -->\n  <artifactId>shared-library</artifactId>\n  <version>0.1.0</version>\n\n  <!-- Inherits from the Jib Multimodule parent POM. -->\n  <parent>\n    <groupId>com.example</groupId>\n    <artifactId>jib-multimodule</artifactId>\n    <version>0.1.0</version>\n  </parent>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <configuration>\n          <!-- we don't want jib to execute on this module -->\n          <skip>true</skip>\n        </configuration>\n      </plugin>\n      <!-- we want this library to be built reproducibly -->\n      <plugin>\n        <groupId>io.github.zlika</groupId>\n        <artifactId>reproducible-build-maven-plugin</artifactId>\n        <version>0.11</version>\n        <executions>\n          <execution>\n            <id>run-when-packaged</id>\n            <goals>\n              <goal>strip-jar</goal>\n            </goals>\n            <phase>package</phase>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "examples/multi-module/shared-library/src/main/java/common/SharedUtils.java",
    "content": "package common;\n\npublic class SharedUtils {\n\n  public static String getText() {\n    return \"A string from 'shared-library'\";\n  }\n}"
  },
  {
    "path": "examples/spring-boot/.mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\nLicensed to the Apache Software Foundation (ASF) under one\nor more contributor license agreements.  See the NOTICE file\ndistributed with this work for additional information\nregarding copyright ownership.  The ASF licenses this file\nto you under the Apache License, Version 2.0 (the\n\"License\"); you may not use this file except in compliance\nwith the License.  You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing,\nsoftware distributed under the License is distributed on an\n\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\nKIND, either express or implied.  See the License for the\nspecific language governing permissions and limitations\nunder the License.\n*/\n\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL =\n            \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: : \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output direcrory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": "examples/spring-boot/.mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip"
  },
  {
    "path": "examples/spring-boot/README.md",
    "content": "# Dockerize a Spring Boot application using Jib\n\nThis is an example of how to easily build a Docker image for a Spring Boot application with Jib.\n\n## Try it yourself\n\nYou can containerize the application with one of the following commands.\n\n**Maven:**\n```shell\n./mvnw compile jib:build -Dimage=<your image, eg. gcr.io/my-project/spring-boot-jib>\n```\n\n**Gradle:**\n```shell\n./gradlew jib --image=<your image, eg. gcr.io/my-project/spring-boot-jib>\n```\n\n## Deploying to Kubernetes using `kubectl`\n\n<!-- Dockerize and deploy a @springboot app to #Kubernetes in seconds @kubernetesio @docker #jib -->\n<p align=\"center\">\n    <a href=\"https://twitter.com/intent/tweet?text=Dockerize+and+deploy+a+%40springboot+app+to+%23Kubernetes+in+seconds+%40kubernetesio+%40docker+%23jib&url=https://asciinema.org/a/192977\">\n    <img src=\"dockerize-spring-boot-jib.gif\" width=\"600\" alt=\"Dockerize Spring Boot app with Jib and deploy to Kubernetes\">\n  </a>\n</p>\n\n*Make sure you have `kubectl` installed and [configured with a cluster](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-cluster).*\n\n```shell\nIMAGE=<your image, eg. gcr.io/my-project/spring-boot-jib>\n\n./mvnw compile jib:build -Dimage=$IMAGE\n\nkubectl run spring-boot-jib --image=$IMAGE --port=8080 --restart=Never\n\n# Wait until pod is running\nkubectl port-forward spring-boot-jib 8080\n```\n```shell\ncurl localhost:8080\n> Greetings from Spring Boot and Jib!\n```\n\n\\* If you are using Gradle, use `./gradlew jib --image=$IMAGE` instead of the `./mvnw` command\n\n<!-- Run a @springboot app on #Kubernetes in seconds @kubernetesio #jib #java -->\nGive it a [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Run+a+%40springboot+app+on+%23Kubernetes+in+seconds+%40kubernetesio+%23jib+%23java&url=https://github.com/GoogleContainerTools/jib/tree/master/examples/spring-boot&hashtags=docker)\n\n## More information\n\nLearn [more about Jib](https://github.com/GoogleContainerTools/jib).\n\n## Build and run on Google Cloud\n\n[![Run on Google Cloud](https://deploy.cloud.run/button.svg)](https://deploy.cloud.run?git_repo=https://github.com/GoogleContainerTools/jib.git&dir=examples/spring-boot)\n"
  },
  {
    "path": "examples/spring-boot/build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'eclipse'\n    id 'idea'\n    id 'org.springframework.boot' version '2.1.6.RELEASE'\n    id 'io.spring.dependency-management' version '1.0.6.RELEASE'\n    id 'com.google.cloud.tools.jib' version '3.5.3'\n}\n\nrepositories {\n    mavenCentral()\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ndependencies {\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n}\n"
  },
  {
    "path": "examples/spring-boot/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/spring-boot/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/spring-boot/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/spring-boot/mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar\"\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        wget \"$jarUrl\" -O \"$wrapperJarPath\"\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        curl -o \"$wrapperJarPath\" \"$jarUrl\"\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "examples/spring-boot/mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Maven2 Start Up Batch script\r\n@REM\r\n@REM Required ENV vars:\r\n@REM JAVA_HOME - location of a JDK home dir\r\n@REM\r\n@REM Optional ENV vars\r\n@REM M2_HOME - location of maven2's installed home dir\r\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\r\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r\n@REM     e.g. to debug Maven itself, use\r\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r\n@echo off\r\n@REM set title of command window\r\ntitle %0\r\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\r\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\r\n\r\n@REM set %HOME% to equivalent of $HOME\r\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\r\n\r\n@REM Execute a user defined script before this one\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\r\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\r\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\r\n:skipRcPre\r\n\r\n@setlocal\r\n\r\nset ERROR_CODE=0\r\n\r\n@REM To isolate internal variables from possible post scripts, we use another setlocal\r\n@setlocal\r\n\r\n@REM ==== START VALIDATION ====\r\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\r\n\r\necho.\r\necho Error: JAVA_HOME not found in your environment. >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n:OkJHome\r\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\r\n\r\necho.\r\necho Error: JAVA_HOME is set to an invalid directory. >&2\r\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n@REM ==== END VALIDATION ====\r\n\r\n:init\r\n\r\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\r\n@REM Fallback to current working directory if not found.\r\n\r\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\r\n\r\nset EXEC_DIR=%CD%\r\nset WDIR=%EXEC_DIR%\r\n:findBaseDir\r\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\r\ncd ..\r\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\r\nset WDIR=%CD%\r\ngoto findBaseDir\r\n\r\n:baseDirFound\r\nset MAVEN_PROJECTBASEDIR=%WDIR%\r\ncd \"%EXEC_DIR%\"\r\ngoto endDetectBaseDir\r\n\r\n:baseDirNotFound\r\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r\ncd \"%EXEC_DIR%\"\r\n\r\n:endDetectBaseDir\r\n\r\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\r\n\r\n@setlocal EnableExtensions EnableDelayedExpansion\r\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r\n\r\n:endReadAdditionalConfig\r\n\r\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\r\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\r\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r\n\r\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar\"\r\nFOR /F \"tokens=1,2 delims==\" %%A IN (%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties) DO (\r\n\tIF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B \r\n)\r\n\r\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\r\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\r\nif exist %WRAPPER_JAR% (\r\n    echo Found %WRAPPER_JAR%\r\n) else (\r\n    echo Couldn't find %WRAPPER_JAR%, downloading it ...\r\n\techo Downloading from: %DOWNLOAD_URL%\r\n    powershell -Command \"(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"\r\n    echo Finished downloading %WRAPPER_JAR%\r\n)\r\n@REM End of extension\r\n\r\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r\nif ERRORLEVEL 1 goto error\r\ngoto end\r\n\r\n:error\r\nset ERROR_CODE=1\r\n\r\n:end\r\n@endlocal & set ERROR_CODE=%ERROR_CODE%\r\n\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\r\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\r\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\r\n:skipRcPost\r\n\r\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\r\n\r\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\r\n\r\nexit /B %ERROR_CODE%\r\n"
  },
  {
    "path": "examples/spring-boot/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>example</groupId>\n    <artifactId>spring-boot-k8s-example</artifactId>\n    <version>0.1.0</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.1.6.RELEASE</version>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n    </dependencies>\n\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>com.google.cloud.tools</groupId>\n                <artifactId>jib-maven-plugin</artifactId>\n                <version>3.5.1</version>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "examples/spring-boot/settings.gradle",
    "content": ""
  },
  {
    "path": "examples/spring-boot/src/main/java/hello/Application.java",
    "content": "package hello;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n    \n    public static void main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n\n}\n"
  },
  {
    "path": "examples/spring-boot/src/main/java/hello/HelloController.java",
    "content": "package hello;\n\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.bind.annotation.RequestMapping;\n\n@RestController\npublic class HelloController {\n    \n    @RequestMapping(\"/\")\n    public String index() {\n        return \"Greetings from Spring Boot and Jib!\";\n    }\n}\n"
  },
  {
    "path": "examples/vertx/README.md",
    "content": "# Containerize a [Eclipse Vert.x](https://vertx.io/) application with Jib\n\nThis is an example of how to easily build a Docker image for a [Eclipse Vert.x application](https://vertx.io/) with Jib.\n\n```shell\n./gradlew jibDockerBuild\n\ndocker run -d --rm -p 8080:8080 vertx-jib-example\n```\n\n## More information\n\nLearn [more about Jib](https://github.com/GoogleContainerTools/jib).\n\nLearn [more about Eclipse Vert.x](https://vertx.io).\n"
  },
  {
    "path": "examples/vertx/build.gradle",
    "content": "plugins {\n    id 'io.vertx.vertx-plugin' version '0.1.0'\n    id 'com.google.cloud.tools.jib' version '3.5.3'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'io.vertx:vertx-web'\n    implementation 'ch.qos.logback:logback-classic:1.2.3'\n}\n\nvertx {\n    vertxVersion = '3.6.0.CR1'\n    mainVerticle = 'example.vertx.MainVerticle'\n    jvmArgs = ['-Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory']\n}\n\njib {\n    container {\n        ports = ['8080']\n    }\n}\n"
  },
  {
    "path": "examples/vertx/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/vertx/gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/vertx/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/vertx/settings.gradle",
    "content": "rootProject.name = 'vertx-jib-example'\n"
  },
  {
    "path": "examples/vertx/src/main/java/example/vertx/MainVerticle.java",
    "content": "package example.vertx;\n\nimport io.vertx.core.AbstractVerticle;\nimport io.vertx.core.Future;\nimport io.vertx.core.Vertx;\nimport io.vertx.core.json.JsonObject;\nimport io.vertx.ext.web.Router;\nimport io.vertx.ext.web.RoutingContext;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class MainVerticle extends AbstractVerticle {\n\n    public static void main(String[] args) {\n        Vertx.vertx().deployVerticle(new MainVerticle());\n    }\n\n    private final Logger logger = LoggerFactory.getLogger(MainVerticle.class);\n\n    @Override\n    public void start(Future<Void> startFuture) {\n        Router router = Router.router(vertx);\n\n        router.get(\"/\").handler(this::hello);\n        router.get(\"/time\").handler(this::now);\n\n        vertx.createHttpServer()\n                .requestHandler(router)\n                .listen(8080, asyncStart -> {\n                    if (asyncStart.succeeded()) {\n                        startFuture.complete();\n                        logger.info(\"HTTP server running on port 8080\");\n                    } else {\n                        logger.error(\"Woops\", asyncStart.cause());\n                        startFuture.fail(asyncStart.cause());\n                    }\n                });\n    }\n\n    private void hello(RoutingContext context) {\n        logger.info(\"Hello request from {}\", context.request().remoteAddress());\n        context.response()\n                .putHeader(\"Content-Type\", \"text/plain\")\n                .end(\"Hello from Vert.x!\");\n    }\n\n    private void now(RoutingContext context) {\n        logger.info(\"Time request from {}\", context.request().remoteAddress());\n        JsonObject data = new JsonObject()\n                .put(\"powered-by\", \"vertx\")\n                .put(\"current-time\", System.currentTimeMillis());\n        context.response()\n                .putHeader(\"Content-Type\", \"application/json\")\n                .end(data.encode());\n    }\n}\n"
  },
  {
    "path": "examples/vertx/src/main/resources/logback.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <logger name=\"io.netty\" level=\"warn\"/>\n    <logger name=\"org.mongodb.driver\" level=\"info\"/>\n    <logger name=\"io.vertx\" level=\"info\"/>\n\n    <root level=\"debug\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n</configuration>"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.9.2-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx1024m\norg.gradle.caching=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "jib-build-plan/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added\n\n- Added explicit `toString()` methods to `FileEntry` and `FilePermissions`. ([#2714](https://github.com/GoogleContainerTools/jib/pull/2714))\n\n### Changed\n\n### Fixed\n\n## 0.4.0\n\n### Changed\n\n- Replaced `BiFunction` usage with `FilePermissionsProvider`, `ModificationTimeProvider` and `OwnershipProvider`. ([#2638](https://github.com/GoogleContainerTools/jib/issues/2638))\n\n## 0.3.1\n\n### Added\n\n- Added `Platform` class representing an image platform. ([#2584](https://github.com/GoogleContainerTools/jib/pull/2584))\n- Added `get/setPlatforms()` and `addPlatform()` to `ContainerBuildPlan` for setting and getting image platforms. ([#2584](https://github.com/GoogleContainerTools/jib/pull/2584))\n\n### Changed\n\n- Removed `get/setOsHint()` and `get/setArchitectureHint()` in favor of `get/setPlatforms()` and `addPlatform()`. ([#2584](https://github.com/GoogleContainerTools/jib/pull/2584))\n\n### Fixed\n\n- Fixed the critical bug that the default `Platform` in `ContainerBuildPlan` has OS and architecture values switched with each other. ([#2597](https://github.com/GoogleContainerTools/jib/pull/2597))\n\n## 0.2.0\n\n### Added\n\n- Added file ownership information in `FileEntry` and `FileEntriesLayer`. ([#2494](https://github.com/GoogleContainerTools/jib/pull/2494))\n"
  },
  {
    "path": "jib-build-plan/build.gradle",
    "content": "plugins {\n  id 'net.researchgate.release'\n  id 'maven-publish'\n  id 'eclipse'\n}\n\ndependencies {\n  compileOnly dependencyStrings.JSR305\n\n  testImplementation dependencyStrings.GUAVA\n  testImplementation dependencyStrings.JUNIT\n  testImplementation dependencyStrings.MOCKITO_CORE\n  testImplementation dependencyStrings.SLF4J_API\n  testImplementation dependencyStrings.SYSTEM_RULES\n}\n\njar {\n  manifest {\n    attributes 'Implementation-Version': archiveVersion\n    attributes 'Automatic-Module-Name': 'com.google.cloud.tools.jib.api.buildplan'\n\n    // OSGi metadata\n    attributes 'Bundle-SymbolicName': 'com.google.cloud.tools.jib.api.buildplan'\n    attributes 'Bundle-Name': 'Jib Container Build Plan API'\n    attributes 'Bundle-Vendor': 'Google LLC'\n    attributes 'Bundle-DocURL': 'https://github.com/GoogleContainerTools/jib'\n    attributes 'Bundle-License': 'https://www.apache.org/licenses/LICENSE-2.0'\n    attributes 'Export-Package': 'com.google.cloud.tools.jib.api.buildplan'\n  }\n}\n\n/* RELEASE */\nconfigureMavenRelease()\n\npublishing {\n  publications {\n    mavenJava(MavenPublication) {\n      pom {\n        name = 'Jib Container Build Plan API'\n        description = 'Jib Container Build Plan API'\n      }\n      from components.java\n    }\n  }\n}\n\n// Release plugin (git release commits and version updates)\nrelease {\n  tagTemplate = 'v$version-build-plan'\n  git {\n    requireBranch = /^build-plan-release-v\\d+.*$/  //regex\n  }\n}\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-build-plan/gradle.properties",
    "content": "version = 0.4.1-SNAPSHOT\n"
  },
  {
    "path": "jib-build-plan/kokoro/release_build.sh",
    "content": "#!/bin/bash\n\n# Fail on any error.\nset -o errexit\n# Display commands to stderr.\nset -o xtrace\n\ncd github/jib\n./gradlew :jib-build-plan:prepareRelease\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/AbsoluteUnixPath.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport com.google.cloud.tools.jib.buildplan.UnixPathParser;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.StringJoiner;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Represents a Unix-style path in absolute form (containing all path components relative to the\n * file system root {@code /}).\n *\n * <p>This class is immutable and thread-safe.\n */\n@Immutable\npublic class AbsoluteUnixPath {\n\n  /**\n   * Gets a new {@link AbsoluteUnixPath} from a Unix-style path string. The path must begin with a\n   * forward slash ({@code /}).\n   *\n   * @param unixPath the Unix-style path string in absolute form\n   * @return a new {@link AbsoluteUnixPath}\n   */\n  public static AbsoluteUnixPath get(String unixPath) {\n    if (!unixPath.startsWith(\"/\")) {\n      throw new IllegalArgumentException(\"Path does not start with forward slash (/): \" + unixPath);\n    }\n\n    return new AbsoluteUnixPath(UnixPathParser.parse(unixPath));\n  }\n\n  /**\n   * Gets a new {@link AbsoluteUnixPath} from a {@link Path}. The {@code path} must be absolute\n   * (indicated by a non-null {@link Path#getRoot}).\n   *\n   * @param path the absolute {@link Path} to convert to an {@link AbsoluteUnixPath}.\n   * @return a new {@link AbsoluteUnixPath}\n   */\n  public static AbsoluteUnixPath fromPath(Path path) {\n    if (path.getRoot() == null) {\n      throw new IllegalArgumentException(\n          \"Cannot create AbsoluteUnixPath from non-absolute Path: \" + path);\n    }\n\n    List<String> pathComponents = new ArrayList<>(path.getNameCount());\n    path.forEach(component -> pathComponents.add(component.toString()));\n    return new AbsoluteUnixPath(pathComponents);\n  }\n\n  /** Path components after the file system root. This should always match {@link #unixPath}. */\n  private final List<String> pathComponents;\n\n  /**\n   * Unix-style path, in absolute form. Does not end with trailing slash, except for the file system\n   * root ({@code /}). This should always match {@link #pathComponents}.\n   */\n  private final String unixPath;\n\n  private AbsoluteUnixPath(List<String> pathComponents) {\n    this.pathComponents = pathComponents;\n\n    StringJoiner pathJoiner = new StringJoiner(\"/\", \"/\", \"\");\n    for (String pathComponent : pathComponents) {\n      pathJoiner.add(pathComponent);\n    }\n    unixPath = pathJoiner.toString();\n  }\n\n  /**\n   * Resolves this path against another relative path.\n   *\n   * @param relativeUnixPath the relative path to resolve against\n   * @return a new {@link AbsoluteUnixPath} representing the resolved path\n   */\n  public AbsoluteUnixPath resolve(RelativeUnixPath relativeUnixPath) {\n    int newSize = pathComponents.size() + relativeUnixPath.getRelativePathComponents().size();\n    List<String> newPathComponents = new ArrayList<>(newSize);\n\n    newPathComponents.addAll(pathComponents);\n    newPathComponents.addAll(relativeUnixPath.getRelativePathComponents());\n    return new AbsoluteUnixPath(newPathComponents);\n  }\n\n  /**\n   * Resolves this path against another relative path (by the name elements of {@code\n   * relativePath}).\n   *\n   * @param relativePath the relative path to resolve against\n   * @return a new {@link AbsoluteUnixPath} representing the resolved path\n   */\n  public AbsoluteUnixPath resolve(Path relativePath) {\n    if (relativePath.getRoot() != null) {\n      throw new IllegalArgumentException(\"Cannot resolve against absolute Path: \" + relativePath);\n    }\n\n    return AbsoluteUnixPath.fromPath(Paths.get(unixPath).resolve(relativePath));\n  }\n\n  /**\n   * Resolves this path against another relative Unix path in string form.\n   *\n   * @param relativeUnixPath the relative path to resolve against\n   * @return a new {@link AbsoluteUnixPath} representing the resolved path\n   */\n  public AbsoluteUnixPath resolve(String relativeUnixPath) {\n    return resolve(RelativeUnixPath.get(relativeUnixPath));\n  }\n\n  /**\n   * Returns the string form of the absolute Unix-style path.\n   *\n   * @return the string form\n   */\n  @Override\n  public String toString() {\n    return unixPath;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof AbsoluteUnixPath)) {\n      return false;\n    }\n    AbsoluteUnixPath otherAbsoluteUnixPath = (AbsoluteUnixPath) other;\n    return unixPath.equals(otherAbsoluteUnixPath.unixPath);\n  }\n\n  @Override\n  public int hashCode() {\n    return unixPath.hashCode();\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/ContainerBuildPlan.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.Immutable;\n\n/** Describes a plan to build a container. */\n@Immutable\npublic class ContainerBuildPlan {\n\n  /** Builder for {@link ContainerBuildPlan}. */\n  public static class Builder {\n\n    private String baseImage = \"scratch\";\n    private Instant creationTime = Instant.EPOCH;\n    private ImageFormat format = ImageFormat.Docker;\n\n    // LinkedHashSet to preserve the order\n    private Set<Platform> platforms =\n        new LinkedHashSet<>(Collections.singleton(new Platform(\"amd64\", \"linux\")));\n\n    // image execution parameters\n    private Map<String, String> environment = new HashMap<>();\n    private Map<String, String> labels = new HashMap<>();\n    private Set<AbsoluteUnixPath> volumes = new HashSet<>();\n    private Set<Port> exposedPorts = new HashSet<>();\n    @Nullable private String user;\n    @Nullable private AbsoluteUnixPath workingDirectory;\n    @Nullable private List<String> entrypoint;\n    @Nullable private List<String> cmd;\n\n    private List<LayerObject> layers = new ArrayList<>();\n\n    private Builder() {}\n\n    /**\n     * Image reference to a base image. The default is {@code scratch}.\n     *\n     * @param baseImage image reference to a base image\n     * @return this\n     */\n    public Builder setBaseImage(String baseImage) {\n      this.baseImage = baseImage;\n      return this;\n    }\n\n    /**\n     * Adds a desired image platform (OS and architecture pair). If the base image reference is a\n     * Docker manifest list or an OCI image index, an image builder may select the base image\n     * matching the given platform. If the base image reference is an image manifest, an image\n     * builder may ignore the given platform and use the platform of the base image or may decide to\n     * raise on error.\n     *\n     * <p>Note that a new build plan starts with \"amd64/linux\" as the default platform. If you want\n     * to reset the default platform instead of adding a new one, use {@link #setPlatforms(Set)}.\n     *\n     * @param architecture architecture (for example, {@code amd64}) to select a base image in case\n     *     of a manifest list\n     * @param os OS (for example, {@code linux}) to select a base image in case of a manifest list\n     * @return this\n     */\n    public Builder addPlatform(String architecture, String os) {\n      platforms.add(new Platform(architecture, os));\n      return this;\n    }\n\n    /**\n     * Sets a desired platform (properties including OS and architecture) list. If the base image\n     * reference is a Docker manifest list or an OCI image index, an image builder may select the\n     * base images matching the given platforms. If the base image reference is an image manifest,\n     * an image builder may ignore the given platforms and use the platform of the base image or may\n     * decide to raise on error.\n     *\n     * <p>Note that a new build plan starts with \"amd64/linux\" as the default platform.\n     *\n     * @param platforms list of platforms to select base images in case of a manifest list\n     * @return this\n     */\n    public Builder setPlatforms(Set<Platform> platforms) {\n      if (platforms.isEmpty()) {\n        throw new IllegalArgumentException(\"platforms set cannot be empty\");\n      }\n      this.platforms = new LinkedHashSet<>(platforms);\n      return this;\n    }\n\n    /**\n     * Sets the container image creation time. The default is {@link Instant#EPOCH}.\n     *\n     * @param creationTime the container image creation time\n     * @return this\n     */\n    public Builder setCreationTime(Instant creationTime) {\n      this.creationTime = creationTime;\n      return this;\n    }\n\n    /**\n     * Sets the format to build the container image as. Use {@link ImageFormat#Docker} for Docker\n     * V2.2 or {@link ImageFormat#OCI} for OCI.\n     *\n     * @param format the {@link ImageFormat}\n     * @return this\n     */\n    public Builder setFormat(ImageFormat format) {\n      this.format = format;\n      return this;\n    }\n\n    /**\n     * Sets the container environment. These environment variables are available to the program\n     * launched by the container entrypoint command. This replaces any previously-set environment\n     * variables. Note that these values are added to the base image values.\n     *\n     * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#env\">{@code\n     * ENV} in Dockerfiles</a> or {@code env} in the <a\n     * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n     * Container spec</a>.\n     *\n     * @param environment a map of environment variable names to values\n     * @return this\n     */\n    public Builder setEnvironment(Map<String, String> environment) {\n      this.environment = new HashMap<>(environment);\n      return this;\n    }\n\n    /**\n     * Adds a variable in the container environment.\n     *\n     * @param name the environment variable name\n     * @param value the environment variable value\n     * @return this\n     * @see #setEnvironment\n     */\n    public Builder addEnvironmentVariable(String name, String value) {\n      environment.put(name, value);\n      return this;\n    }\n\n    /**\n     * Sets the directories that may hold externally mounted volumes. Note that these values are\n     * added to the base image values.\n     *\n     * <p>This is similar to <a\n     * href=\"https://docs.docker.com/engine/reference/builder/#volume\">{@code VOLUME} in\n     * Dockerfiles</a>.\n     *\n     * @param volumes the directory paths on the container filesystem to set as volumes\n     * @return this\n     */\n    public Builder setVolumes(Set<AbsoluteUnixPath> volumes) {\n      this.volumes = new HashSet<>(volumes);\n      return this;\n    }\n\n    /**\n     * Adds a directory that may hold an externally mounted volume.\n     *\n     * @param volume a directory path on the container filesystem to represent a volume\n     * @return this\n     * @see #setVolumes(Set)\n     */\n    public Builder addVolume(AbsoluteUnixPath volume) {\n      volumes.add(volume);\n      return this;\n    }\n\n    /**\n     * Sets the labels for the container. This replaces any previously-set labels. Note that these\n     * values are added to the base image values.\n     *\n     * <p>This is similar to <a\n     * href=\"https://docs.docker.com/engine/reference/builder/#label\">{@code LABEL} in\n     * Dockerfiles</a>.\n     *\n     * @param labels a map of label keys to values\n     * @return this\n     */\n    public Builder setLabels(Map<String, String> labels) {\n      this.labels = new HashMap<>(labels);\n      return this;\n    }\n\n    /**\n     * Sets a label for the container.\n     *\n     * @param key the label key\n     * @param value the label value\n     * @return this\n     */\n    public Builder addLabel(String key, String value) {\n      labels.put(key, value);\n      return this;\n    }\n\n    /**\n     * Sets the ports to expose from the container. Ports exposed will allow ingress traffic. This\n     * replaces any previously-set exposed ports. Note that these values are added to the base image\n     * values.\n     *\n     * <p>Use {@link Port#tcp} to expose a port for TCP traffic and {@link Port#udp} to expose a\n     * port for UDP traffic.\n     *\n     * <p>This is similar to <a\n     * href=\"https://docs.docker.com/engine/reference/builder/#expose\">{@code EXPOSE} in\n     * Dockerfiles</a> or {@code ports} in the <a\n     * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n     * Container spec</a>.\n     *\n     * @param exposedPorts the ports to expose\n     * @return this\n     */\n    public Builder setExposedPorts(Set<Port> exposedPorts) {\n      this.exposedPorts = new HashSet<>(exposedPorts);\n      return this;\n    }\n\n    /**\n     * Adds a port to expose from the container.\n     *\n     * @param exposedPort the port to expose\n     * @return this\n     * @see #setExposedPorts(Set)\n     */\n    public Builder addExposedPort(Port exposedPort) {\n      exposedPorts.add(exposedPort);\n      return this;\n    }\n\n    /**\n     * Sets the user and group to run the container as. {@code user} can be a username or UID along\n     * with an optional groupname or GID. {@code null} signals to use the base image value.\n     *\n     * <p>The following are valid formats for {@code user}\n     *\n     * <ul>\n     *   <li>{@code user}\n     *   <li>{@code uid}\n     *   <li>{@code :group}\n     *   <li>{@code :gid}\n     *   <li>{@code user:group}\n     *   <li>{@code uid:gid}\n     *   <li>{@code uid:group}\n     *   <li>{@code user:gid}\n     * </ul>\n     *\n     * @param user the user to run the container as\n     * @return this\n     */\n    public Builder setUser(@Nullable String user) {\n      this.user = user;\n      return this;\n    }\n\n    /**\n     * Sets the working directory in the container. {@code null} signals to use the base image\n     * value.\n     *\n     * @param workingDirectory the working directory\n     * @return this\n     */\n    public Builder setWorkingDirectory(@Nullable AbsoluteUnixPath workingDirectory) {\n      this.workingDirectory = workingDirectory;\n      return this;\n    }\n\n    /**\n     * Sets the container entrypoint. This is the beginning of the command that is run when the\n     * container starts. {@link #setCmd} sets additional tokens. {@code null} signals to use the\n     * base image value.\n     *\n     * <p>This is similar to <a\n     * href=\"https://docs.docker.com/engine/reference/builder/#exec-form-entrypoint-example\">{@code\n     * ENTRYPOINT} in Dockerfiles</a> or {@code command} in the <a\n     * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n     * Container spec</a>.\n     *\n     * @param entrypoint a list of the entrypoint command\n     * @return this\n     */\n    public Builder setEntrypoint(@Nullable List<String> entrypoint) {\n      if (entrypoint == null) {\n        this.entrypoint = null;\n      } else {\n        this.entrypoint = new ArrayList<>(entrypoint);\n      }\n      return this;\n    }\n\n    /**\n     * Sets the container entrypoint program arguments. These are additional tokens added to the end\n     * of the entrypoint command. {@code null} signals to use the base image value (only when\n     * entrypoint is also {@code null}).\n     *\n     * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#cmd\">{@code\n     * CMD} in Dockerfiles</a> or {@code args} in the <a\n     * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n     * Container spec</a>.\n     *\n     * <p>For example, if the entrypoint was {@code myprogram --flag subcommand} and program\n     * arguments were {@code hello world}, then the command that run when the container starts is\n     * {@code myprogram --flag subcommand hello world}.\n     *\n     * @param cmd a list of program argument tokens\n     * @return this\n     */\n    public Builder setCmd(@Nullable List<String> cmd) {\n      if (cmd == null) {\n        this.cmd = null;\n      } else {\n        this.cmd = new ArrayList<>(cmd);\n      }\n      return this;\n    }\n\n    public Builder addLayer(LayerObject layer) {\n      layers.add(layer);\n      return this;\n    }\n\n    public Builder setLayers(List<? extends LayerObject> layer) {\n      layers = new ArrayList<>(layer);\n      return this;\n    }\n\n    /**\n     * Returns the built {@link ContainerBuildPlan}.\n     *\n     * @return container build plan\n     */\n    public ContainerBuildPlan build() {\n      return new ContainerBuildPlan(\n          baseImage,\n          platforms,\n          creationTime,\n          format,\n          environment,\n          labels,\n          volumes,\n          exposedPorts,\n          user,\n          workingDirectory,\n          entrypoint,\n          cmd,\n          layers);\n    }\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private final String baseImage;\n  private final Set<Platform> platforms;\n  private final Instant creationTime;\n  private final ImageFormat format;\n\n  // image execution parameters\n  private final Map<String, String> environment;\n  private final Map<String, String> labels;\n  private final Set<AbsoluteUnixPath> volumes;\n  private final Set<Port> exposedPorts;\n  @Nullable private final String user;\n  @Nullable private final AbsoluteUnixPath workingDirectory;\n  @Nullable private final List<String> entrypoint;\n  @Nullable private final List<String> cmd;\n\n  private final List<LayerObject> layers;\n\n  private ContainerBuildPlan(\n      String baseImage,\n      Set<Platform> platforms,\n      Instant creationTime,\n      ImageFormat format,\n      Map<String, String> environment,\n      Map<String, String> labels,\n      Set<AbsoluteUnixPath> volumes,\n      Set<Port> exposedPorts,\n      @Nullable String user,\n      @Nullable AbsoluteUnixPath workingDirectory,\n      @Nullable List<String> entrypoint,\n      @Nullable List<String> cmd,\n      List<LayerObject> layers) {\n    this.baseImage = baseImage;\n    this.platforms = platforms;\n    this.creationTime = creationTime;\n    this.format = format;\n    this.environment = environment;\n    this.labels = labels;\n    this.volumes = volumes;\n    this.exposedPorts = exposedPorts;\n    this.user = user;\n    this.workingDirectory = workingDirectory;\n    this.entrypoint = entrypoint;\n    this.cmd = cmd;\n    this.layers = layers;\n  }\n\n  public String getBaseImage() {\n    return baseImage;\n  }\n\n  /**\n   * Creates and returns a default platform if the user hasn't added or set any platforms ,else\n   * returns a list of user specified platforms .\n   *\n   * @return platforms a list of user specified platforms.\n   */\n  public Set<Platform> getPlatforms() {\n    return new LinkedHashSet<>(platforms);\n  }\n\n  public ImageFormat getFormat() {\n    return format;\n  }\n\n  public Instant getCreationTime() {\n    return creationTime;\n  }\n\n  public Map<String, String> getEnvironment() {\n    return new HashMap<>(environment);\n  }\n\n  public Set<AbsoluteUnixPath> getVolumes() {\n    return new HashSet<>(volumes);\n  }\n\n  public Map<String, String> getLabels() {\n    return new HashMap<>(labels);\n  }\n\n  public Set<Port> getExposedPorts() {\n    return new HashSet<>(exposedPorts);\n  }\n\n  @Nullable\n  public String getUser() {\n    return user;\n  }\n\n  @Nullable\n  public AbsoluteUnixPath getWorkingDirectory() {\n    return workingDirectory;\n  }\n\n  @Nullable\n  public List<String> getEntrypoint() {\n    return entrypoint == null ? null : new ArrayList<>(entrypoint);\n  }\n\n  @Nullable\n  public List<String> getCmd() {\n    return cmd == null ? null : new ArrayList<>(cmd);\n  }\n\n  public List<? extends LayerObject> getLayers() {\n    return new ArrayList<>(layers);\n  }\n\n  /**\n   * Creates a builder configured with the current values.\n   *\n   * @return {@link Builder} configured with the current values.\n   */\n  public Builder toBuilder() {\n    return builder()\n        .setBaseImage(baseImage)\n        .setPlatforms(platforms)\n        .setCreationTime(creationTime)\n        .setFormat(format)\n        .setEnvironment(environment)\n        .setLabels(labels)\n        .setVolumes(volumes)\n        .setExposedPorts(exposedPorts)\n        .setUser(user)\n        .setWorkingDirectory(workingDirectory)\n        .setEntrypoint(entrypoint)\n        .setCmd(cmd)\n        .setLayers(layers);\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/FileEntriesLayer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.concurrent.Immutable;\n\n/** Configures how to build a layer in the container image. Instantiate with {@link #builder}. */\n@Immutable\npublic class FileEntriesLayer implements LayerObject {\n\n  /** Builds a {@link FileEntriesLayer}. */\n  public static class Builder {\n\n    private String name = \"\";\n    private List<FileEntry> entries = new ArrayList<>();\n\n    private Builder() {}\n\n    /**\n     * Sets a name for this layer. This name does not affect the contents of the layer.\n     *\n     * @param name the name\n     * @return this\n     */\n    public Builder setName(String name) {\n      this.name = name;\n      return this;\n    }\n\n    /**\n     * Sets entries for the layer.\n     *\n     * @param entries file entries in the layer\n     * @return this\n     */\n    public Builder setEntries(List<FileEntry> entries) {\n      this.entries = new ArrayList<>(entries);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer.\n     *\n     * @param entry the layer entry to add\n     * @return this\n     */\n    public Builder addEntry(FileEntry entry) {\n      entries.add(entry);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer. Only adds the single source file to the exact path in the\n     * container file system.\n     *\n     * <p>For example, {@code addEntry(Paths.get(\"myfile\"),\n     * AbsoluteUnixPath.get(\"/path/in/container\"))} adds a file {@code myfile} to the container file\n     * system at {@code /path/in/container}.\n     *\n     * <p>For example, {@code addEntry(Paths.get(\"mydirectory\"),\n     * AbsoluteUnixPath.get(\"/path/in/container\"))} adds a directory {@code mydirectory/} to the\n     * container file system at {@code /path/in/container/}. This does <b>not</b> add the contents\n     * of {@code mydirectory}.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @return this\n     */\n    public Builder addEntry(Path sourceFile, AbsoluteUnixPath pathInContainer) {\n      return addEntry(\n          sourceFile,\n          pathInContainer,\n          DEFAULT_FILE_PERMISSIONS_PROVIDER.get(sourceFile, pathInContainer));\n    }\n\n    /**\n     * Adds an entry to the layer with the given permissions. Only adds the single source file to\n     * the exact path in the container file system. See {@link Builder#addEntry(Path,\n     * AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param permissions the file permissions on the container\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     * @see FilePermissions#DEFAULT_FILE_PERMISSIONS\n     * @see FilePermissions#DEFAULT_FOLDER_PERMISSIONS\n     */\n    public Builder addEntry(\n        Path sourceFile, AbsoluteUnixPath pathInContainer, FilePermissions permissions) {\n      return addEntry(sourceFile, pathInContainer, permissions, DEFAULT_MODIFICATION_TIME);\n    }\n\n    /**\n     * Adds an entry to the layer with the given file modification time. Only adds the single source\n     * file to the exact path in the container file system. See {@link Builder#addEntry(Path,\n     * AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param modificationTime the file modification time\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     */\n    public Builder addEntry(\n        Path sourceFile, AbsoluteUnixPath pathInContainer, Instant modificationTime) {\n      return addEntry(\n          sourceFile,\n          pathInContainer,\n          DEFAULT_FILE_PERMISSIONS_PROVIDER.get(sourceFile, pathInContainer),\n          modificationTime);\n    }\n\n    /**\n     * Adds an entry to the layer with the given permissions and file modification time. Only adds\n     * the single source file to the exact path in the container file system. See {@link\n     * Builder#addEntry(Path, AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param permissions the file permissions on the container\n     * @param modificationTime the file modification time\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     * @see FilePermissions#DEFAULT_FILE_PERMISSIONS\n     * @see FilePermissions#DEFAULT_FOLDER_PERMISSIONS\n     */\n    public Builder addEntry(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissions permissions,\n        Instant modificationTime) {\n      return addEntry(new FileEntry(sourceFile, pathInContainer, permissions, modificationTime));\n    }\n\n    /**\n     * Adds an entry to the layer with the given permissions and file modification time. Only adds\n     * the single source file to the exact path in the container file system. See {@link\n     * Builder#addEntry(Path, AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param permissions the file permissions on the container\n     * @param modificationTime the file modification time\n     * @param ownership file ownership. For example, \"1234\", \"user\", \":5678\", \":group\", \"1234:5678\",\n     *     and \"user:group\". Note that \"\" (empty string), \":\" (single colon), \"0:\", \":0\" are allowed\n     *     and representative of \"0:0\" or \"root:root\", but prefer an empty string for \"0:0\".\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     * @see FilePermissions#DEFAULT_FILE_PERMISSIONS\n     * @see FilePermissions#DEFAULT_FOLDER_PERMISSIONS\n     */\n    public Builder addEntry(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissions permissions,\n        Instant modificationTime,\n        String ownership) {\n      return addEntry(\n          new FileEntry(sourceFile, pathInContainer, permissions, modificationTime, ownership));\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * <p>For example, {@code addEntryRecursive(Paths.get(\"mydirectory\",\n     * AbsoluteUnixPath.get(\"/path/in/container\"))} adds {@code mydirectory} to the container file\n     * system at {@code /path/in/container} such that {@code mydirectory/subfile} is found at {@code\n     * /path/in/container/subfile}.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(Path sourceFile, AbsoluteUnixPath pathInContainer)\n        throws IOException {\n      return addEntryRecursive(sourceFile, pathInContainer, DEFAULT_FILE_PERMISSIONS_PROVIDER);\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param filePermissionProvider a provider that takes a source path and destination path on the\n     *     container and returns the file permissions that should be set for that path\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissionsProvider filePermissionProvider)\n        throws IOException {\n      return addEntryRecursive(\n          sourceFile, pathInContainer, filePermissionProvider, DEFAULT_MODIFICATION_TIME_PROVIDER);\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param filePermissionProvider a provider that takes a source path and destination path on the\n     *     container and returns the file permissions that should be set for that path\n     * @param modificationTimeProvider a provider that takes a source path and destination path on\n     *     the container and returns the file modification time that should be set for that path\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissionsProvider filePermissionProvider,\n        ModificationTimeProvider modificationTimeProvider)\n        throws IOException {\n      return addEntryRecursive(\n          sourceFile,\n          pathInContainer,\n          filePermissionProvider,\n          modificationTimeProvider,\n          DEFAULT_OWNERSHIP_PROVIDER);\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param filePermissionProvider a provider that takes a source path and destination path on the\n     *     container and returns the file permissions that should be set for that path\n     * @param modificationTimeProvider a provider that takes a source path and destination path on\n     *     the container and returns the file modification time that should be set for that path\n     * @param ownershipProvider a provider that takes a source path and destination path on the\n     *     container and returns the ownership that should be set for that path\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissionsProvider filePermissionProvider,\n        ModificationTimeProvider modificationTimeProvider,\n        OwnershipProvider ownershipProvider)\n        throws IOException {\n      FilePermissions permissions = filePermissionProvider.get(sourceFile, pathInContainer);\n      Instant modificationTime = modificationTimeProvider.get(sourceFile, pathInContainer);\n      String ownership = ownershipProvider.get(sourceFile, pathInContainer);\n      addEntry(sourceFile, pathInContainer, permissions, modificationTime, ownership);\n      if (!Files.isDirectory(sourceFile)) {\n        return this;\n      }\n      try (Stream<Path> files = Files.list(sourceFile)) {\n        for (Path file : files.collect(Collectors.toList())) {\n          addEntryRecursive(\n              file,\n              pathInContainer.resolve(file.getFileName()),\n              filePermissionProvider,\n              modificationTimeProvider,\n              ownershipProvider);\n        }\n      }\n      return this;\n    }\n\n    /**\n     * Returns the built {@link FileEntriesLayer}.\n     *\n     * @return the built {@link FileEntriesLayer}\n     */\n    public FileEntriesLayer build() {\n      return new FileEntriesLayer(name, entries);\n    }\n  }\n\n  /** Provider that returns default file permissions (644 for files, 755 for directories). */\n  public static final FilePermissionsProvider DEFAULT_FILE_PERMISSIONS_PROVIDER =\n      (sourcePath, destinationPath) ->\n          Files.isDirectory(sourcePath)\n              ? FilePermissions.DEFAULT_FOLDER_PERMISSIONS\n              : FilePermissions.DEFAULT_FILE_PERMISSIONS;\n\n  /** Default file modification time (EPOCH + 1 second). */\n  public static final Instant DEFAULT_MODIFICATION_TIME = Instant.ofEpochSecond(1);\n\n  /** Provider that returns default file modification time (EPOCH + 1 second). */\n  public static final ModificationTimeProvider DEFAULT_MODIFICATION_TIME_PROVIDER =\n      (sourcePath, destinationPath) -> DEFAULT_MODIFICATION_TIME;\n\n  /**\n   * Provider that returns default file ownership (an empty string \"\" effectively representing\n   * \"0:0\").\n   */\n  public static final OwnershipProvider DEFAULT_OWNERSHIP_PROVIDER =\n      (sourcePath, destinationPath) -> \"\";\n\n  /**\n   * Gets a new {@link Builder} for {@link FileEntriesLayer}.\n   *\n   * @return a new {@link Builder}\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private final String name;\n  private final List<FileEntry> entries;\n\n  /**\n   * Use {@link #builder} to instantiate.\n   *\n   * @param name an optional name for the layer\n   * @param entries the list of {@link FileEntry}s\n   */\n  private FileEntriesLayer(String name, List<FileEntry> entries) {\n    this.name = name;\n    this.entries = entries;\n  }\n\n  @Override\n  public Type getType() {\n    return Type.FILE_ENTRIES;\n  }\n\n  /**\n   * Gets the name.\n   *\n   * @return the name\n   */\n  @Override\n  public String getName() {\n    return name;\n  }\n\n  /**\n   * Gets the list of entries.\n   *\n   * @return the list of entries\n   */\n  public List<FileEntry> getEntries() {\n    return new ArrayList<>(entries);\n  }\n\n  /**\n   * Creates a builder configured with the current values.\n   *\n   * @return {@link Builder} configured with the current values\n   */\n  public Builder toBuilder() {\n    return builder().setName(name).setEntries(entries);\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/FileEntry.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.Objects;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Represents an entry in the layer. A layer consists of many entries that can be converted into tar\n * archive entries.\n *\n * <p>This class is immutable and thread-safe.\n */\n@Immutable\npublic class FileEntry {\n\n  private final Path sourceFile;\n  private final AbsoluteUnixPath extractionPath;\n  private final FilePermissions permissions;\n  private final Instant modificationTime;\n  private final String ownership;\n\n  /**\n   * Instantiates with a source file and the path to place the source file in the container file\n   * system.\n   *\n   * <p>For example, {@code new FileEntry(Paths.get(\"HelloWorld.class\"),\n   * AbsoluteUnixPath.get(\"/app/classes/HelloWorld.class\"))} adds a file {@code HelloWorld.class} to\n   * the container file system at {@code /app/classes/HelloWorld.class}.\n   *\n   * <p>For example, {@code new FileEntry(Paths.get(\"com\"),\n   * AbsoluteUnixPath.get(\"/app/classes/com\"))} adds a directory to the container file system at\n   * {@code /app/classes/com}. This does <b>not</b> add the contents of {@code com/}.\n   *\n   * <p>Note that:\n   *\n   * <ul>\n   *   <li>Entry source files can be either files or directories.\n   *   <li>Adding a directory does not include the contents of the directory. Each file under a\n   *       directory must be added as a separate {@link FileEntry}.\n   * </ul>\n   *\n   * @param sourceFile the source file to add to the layer\n   * @param extractionPath the path in the container file system corresponding to the {@code\n   *     sourceFile}\n   * @param permissions the file permissions on the container\n   * @param modificationTime the file modification time\n   */\n  public FileEntry(\n      Path sourceFile,\n      AbsoluteUnixPath extractionPath,\n      FilePermissions permissions,\n      Instant modificationTime) {\n    this.sourceFile = sourceFile;\n    this.extractionPath = extractionPath;\n    this.permissions = permissions;\n    this.modificationTime = modificationTime;\n    ownership = \"\";\n  }\n\n  /**\n   * Instantiates with a source file and the path to place the source file in the container file\n   * system. See {@link #FileEntry(Path, AbsoluteUnixPath, FilePermissions, Instant)} for more\n   * information.\n   *\n   * @param sourceFile the source file to add to the layer\n   * @param extractionPath the path in the container file system corresponding to the {@code\n   *     sourceFile}\n   * @param permissions the file permissions on the container\n   * @param modificationTime the file modification time\n   * @param ownership file ownership. For example, \"1234\", \"user\", \":5678\", \":group\", \"1234:5678\",\n   *     and \"user:group\". Note that \"\" (empty string), \":\" (single colon), \"0:\", \":0\" are allowed\n   *     and representative of \"0:0\" or \"root:root\", but prefer an empty string for \"0:0\".\n   */\n  public FileEntry(\n      Path sourceFile,\n      AbsoluteUnixPath extractionPath,\n      FilePermissions permissions,\n      Instant modificationTime,\n      String ownership) {\n    this.sourceFile = sourceFile;\n    this.extractionPath = extractionPath;\n    this.permissions = permissions;\n    this.modificationTime = modificationTime;\n    this.ownership = ownership;\n  }\n\n  /**\n   * Returns the modification time of the file in the entry.\n   *\n   * @return the modification time\n   */\n  public Instant getModificationTime() {\n    return modificationTime;\n  }\n\n  /**\n   * Gets the source file. The source file may be relative or absolute, so the caller should use\n   * {@code getSourceFile().toAbsolutePath().toString()} for the serialized form since the\n   * serialization could change independently of the path representation.\n   *\n   * @return the source file\n   */\n  public Path getSourceFile() {\n    return sourceFile;\n  }\n\n  /**\n   * Gets the extraction path.\n   *\n   * @return the extraction path\n   */\n  public AbsoluteUnixPath getExtractionPath() {\n    return extractionPath;\n  }\n\n  /**\n   * Gets the file permissions on the container.\n   *\n   * @return the file permissions on the container\n   */\n  public FilePermissions getPermissions() {\n    return permissions;\n  }\n\n  /**\n   * Gets the file ownership on the container.\n   *\n   * @return the file ownership on the container\n   */\n  public String getOwnership() {\n    return ownership;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof FileEntry)) {\n      return false;\n    }\n    FileEntry otherFileEntry = (FileEntry) other;\n    return sourceFile.equals(otherFileEntry.sourceFile)\n        && extractionPath.equals(otherFileEntry.extractionPath)\n        && permissions.equals(otherFileEntry.permissions)\n        && modificationTime.equals(otherFileEntry.modificationTime)\n        && ownership.equals(otherFileEntry.ownership);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(sourceFile, extractionPath, permissions, modificationTime, ownership);\n  }\n\n  @Override\n  public String toString() {\n    return \"{\"\n        + sourceFile\n        + \",\"\n        + extractionPath\n        + \",\"\n        + permissions\n        + \",\"\n        + modificationTime\n        + \",\"\n        + ownership\n        + \"}\";\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/FilePermissions.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Represents read/write/execute file permissions for owner, group, and others.\n *\n * <p>This class is immutable and thread-safe.\n */\n@Immutable\npublic class FilePermissions {\n\n  /** Default permissions for files added to the container. */\n  public static final FilePermissions DEFAULT_FILE_PERMISSIONS = new FilePermissions(0644);\n\n  /** Default permissions for folders added to the container. */\n  public static final FilePermissions DEFAULT_FOLDER_PERMISSIONS = new FilePermissions(0755);\n\n  /**\n   * Matches an octal string representation of file permissions. From left to right, each digit\n   * represents permissions for owner, group, and other.\n   */\n  private static final String OCTAL_PATTERN = \"[0-7][0-7][0-7]\";\n\n  /** Maps from a {@link PosixFilePermission} to its corresponding file permission bit. */\n  private static final Map<PosixFilePermission, Integer> PERMISSION_MAP;\n\n  static {\n    Map<PosixFilePermission, Integer> map = new HashMap<>(9);\n    map.put(PosixFilePermission.OWNER_READ, 0400);\n    map.put(PosixFilePermission.OWNER_WRITE, 0200);\n    map.put(PosixFilePermission.OWNER_EXECUTE, 0100);\n    map.put(PosixFilePermission.GROUP_READ, 040);\n    map.put(PosixFilePermission.GROUP_WRITE, 020);\n    map.put(PosixFilePermission.GROUP_EXECUTE, 010);\n    map.put(PosixFilePermission.OTHERS_READ, 04);\n    map.put(PosixFilePermission.OTHERS_WRITE, 02);\n    map.put(PosixFilePermission.OTHERS_EXECUTE, 01);\n    PERMISSION_MAP = Collections.unmodifiableMap(map);\n  }\n\n  /**\n   * Creates a new {@link FilePermissions} from an octal string representation (e.g. \"123\", \"644\",\n   * \"755\", etc).\n   *\n   * @param octalPermissions the octal string representation of the permissions\n   * @return a new {@link FilePermissions} with the given permissions\n   */\n  public static FilePermissions fromOctalString(String octalPermissions) {\n    if (!octalPermissions.matches(OCTAL_PATTERN)) {\n      throw new IllegalArgumentException(\n          \"octalPermissions must be a 3-digit octal number (000-777)\");\n    }\n    return new FilePermissions(Integer.parseInt(octalPermissions, 8));\n  }\n\n  /**\n   * Creates a new {@link FilePermissions} from a set of {@link PosixFilePermission}.\n   *\n   * @param posixFilePermissions the set of {@link PosixFilePermission}\n   * @return a new {@link FilePermissions} with the given permissions\n   */\n  public static FilePermissions fromPosixFilePermissions(\n      Set<PosixFilePermission> posixFilePermissions) {\n    int permissionBits = 0;\n    for (PosixFilePermission permission : posixFilePermissions) {\n      permissionBits |= Objects.requireNonNull(PERMISSION_MAP.get(permission));\n    }\n    return new FilePermissions(permissionBits);\n  }\n\n  private final int permissionBits;\n\n  // VisibleForTesting\n  FilePermissions(int permissionBits) {\n    this.permissionBits = permissionBits;\n  }\n\n  /**\n   * Gets the corresponding permissions bits specified by the {@link FilePermissions}.\n   *\n   * @return the permission bits\n   */\n  public int getPermissionBits() {\n    return permissionBits;\n  }\n\n  /**\n   * Gets the octal string representation of the permissions.\n   *\n   * @return the octal string representation of the permissions\n   */\n  public String toOctalString() {\n    return Integer.toString(permissionBits, 8);\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof FilePermissions)) {\n      return false;\n    }\n    FilePermissions otherFilePermissions = (FilePermissions) other;\n    return permissionBits == otherFilePermissions.permissionBits;\n  }\n\n  @Override\n  public int hashCode() {\n    return permissionBits;\n  }\n\n  @Override\n  public String toString() {\n    return toOctalString();\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/FilePermissionsProvider.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.nio.file.Path;\n\n/** Interface for providing rules to determine file permissions on a container. */\n@FunctionalInterface\npublic interface FilePermissionsProvider {\n\n  /**\n   * Returns the file permissions that should be set for a path, given the source path and\n   * destination path on a container.\n   *\n   * @param sourcePath the source file.\n   * @param destinationPath the destination path. The path in the container file system\n   *     corresponding to sourcePath.\n   * @return the permissions to be set for the file.\n   */\n  public FilePermissions get(Path sourcePath, AbsoluteUnixPath destinationPath);\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/ImageFormat.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\n/** Indicates the format of the image. */\npublic enum ImageFormat {\n\n  /** See <a href=\"https://docs.docker.com/registry/spec/manifest-v2-2/\">Docker V2.2</a>. */\n  Docker,\n\n  /** See <a href=\"https://github.com/opencontainers/image-spec/blob/master/manifest.md\">OCI</a>. */\n  OCI\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/LayerObject.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Serves as a base class for the \"layers\" property in the build plan specification.\n *\n * <ul>\n *   <li>{@link Type#FILE_ENTRIES} indicates {@link FileEntriesLayer}.\n * </ul>\n */\n@Immutable\npublic interface LayerObject {\n\n  public static enum Type {\n    FILE_ENTRIES,\n  }\n\n  public Type getType();\n\n  public String getName();\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/ModificationTimeProvider.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.nio.file.Path;\nimport java.time.Instant;\n\n/** Interface for providing the file modification time on a container. */\n@FunctionalInterface\npublic interface ModificationTimeProvider {\n\n  /**\n   * Returns the file modification time that should be set on a path, given the source path and\n   * destination path.\n   *\n   * @param sourcePath the source file.\n   * @param destinationPath the destination path. The path in the container file system\n   *     corresponding to sourcePath.\n   * @return the file modification time.\n   */\n  public Instant get(Path sourcePath, AbsoluteUnixPath destinationPath);\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/OwnershipProvider.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.nio.file.Path;\n\n/** Interface for providing the file ownership on a container. */\n@FunctionalInterface\npublic interface OwnershipProvider {\n\n  /**\n   * Returns the file ownership that should be set for a path, given the source path and destination\n   * path on a container.\n   *\n   * @param sourcePath the source file.\n   * @param destinationPath the destination path. The path in the container file system\n   *     corresponding to sourcePath.\n   * @return the ownership to be set for the file.\n   */\n  public String get(Path sourcePath, AbsoluteUnixPath destinationPath);\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/Platform.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.util.Objects;\nimport javax.annotation.concurrent.Immutable;\n\n/** Represents an image platform (for example, \"amd64/linux\"). */\n@Immutable\npublic class Platform {\n  private final String architecture;\n  private final String os;\n\n  public Platform(String architecture, String os) {\n    this.architecture = architecture;\n    this.os = os;\n  }\n\n  public String getArchitecture() {\n    return architecture;\n  }\n\n  public String getOs() {\n    return os;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof Platform)) {\n      return false;\n    }\n    Platform otherPlatform = (Platform) other;\n    return architecture.equals(otherPlatform.getArchitecture()) && os.equals(otherPlatform.getOs());\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(architecture, os);\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/Port.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.util.Objects;\nimport javax.annotation.concurrent.Immutable;\n\n/** Represents a port number with a protocol (TCP or UDP). */\n@Immutable\npublic class Port {\n\n  private static final String TCP_PROTOCOL = \"tcp\";\n  private static final String UDP_PROTOCOL = \"udp\";\n\n  /**\n   * Create a new {@link Port} with TCP protocol.\n   *\n   * @param port the port number\n   * @return the new {@link Port}\n   */\n  public static Port tcp(int port) {\n    return new Port(port, TCP_PROTOCOL);\n  }\n\n  /**\n   * Create a new {@link Port} with UDP protocol.\n   *\n   * @param port the port number\n   * @return the new {@link Port}\n   */\n  public static Port udp(int port) {\n    return new Port(port, UDP_PROTOCOL);\n  }\n\n  /**\n   * Gets a {@link Port} with protocol parsed from the string form {@code protocolString}. Unknown\n   * protocols will default to TCP.\n   *\n   * @param port the port number\n   * @param protocolString the case insensitive string (e.g. \"tcp\", \"udp\")\n   * @return the {@link Port}\n   */\n  public static Port parseProtocol(int port, String protocolString) {\n    String protocol = UDP_PROTOCOL.equalsIgnoreCase(protocolString) ? UDP_PROTOCOL : TCP_PROTOCOL;\n    return new Port(port, protocol);\n  }\n\n  private final int portNumber;\n  private final String protocol;\n\n  private Port(int portNumber, String protocol) {\n    this.portNumber = portNumber;\n    this.protocol = protocol;\n  }\n\n  /**\n   * Gets the port number.\n   *\n   * @return the port number\n   */\n  public int getPort() {\n    return portNumber;\n  }\n\n  /**\n   * Gets the protocol.\n   *\n   * @return the protocol\n   */\n  public String getProtocol() {\n    return protocol;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (other == this) {\n      return true;\n    }\n    if (!(other instanceof Port)) {\n      return false;\n    }\n    Port otherPort = (Port) other;\n    return portNumber == otherPort.portNumber && protocol.equals(otherPort.protocol);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(portNumber, protocol);\n  }\n\n  /**\n   * Stringifies the port with protocol, in the form {@code <port>/<protocol>}. For example: {@code\n   * 1337/TCP}.\n   *\n   * @return the string form of the port with protocol\n   */\n  @Override\n  public String toString() {\n    return portNumber + \"/\" + protocol;\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/api/buildplan/RelativeUnixPath.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport com.google.cloud.tools.jib.buildplan.UnixPathParser;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Represents a Unix-style path in relative form (does not start at the file system root {@code /}).\n *\n * <p>This class is immutable and thread-safe.\n */\n@Immutable\npublic class RelativeUnixPath {\n\n  /**\n   * Gets a new {@link RelativeUnixPath} from a Unix-style path in relative form. The {@code path}\n   * must be relative (does not begin with a leading slash {@code /}).\n   *\n   * @param relativePath the relative path\n   * @return a new {@link RelativeUnixPath}\n   */\n  public static RelativeUnixPath get(String relativePath) {\n    if (relativePath.startsWith(\"/\")) {\n      throw new IllegalArgumentException(\"Path starts with forward slash (/): \" + relativePath);\n    }\n\n    return new RelativeUnixPath(UnixPathParser.parse(relativePath));\n  }\n\n  private final List<String> pathComponents;\n\n  /** Instantiate with {@link #get}. */\n  private RelativeUnixPath(List<String> pathComponents) {\n    this.pathComponents = pathComponents;\n  }\n\n  /**\n   * Gets the relative Unix path this represents, in a list of components.\n   *\n   * @return the relative path this represents, in a list of components\n   */\n  List<String> getRelativePathComponents() {\n    return new ArrayList<>(pathComponents);\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/main/java/com/google/cloud/tools/jib/buildplan/UnixPathParser.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.buildplan;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/** Parses Unix-style paths. */\npublic class UnixPathParser {\n\n  /**\n   * Parses a Unix-style path into a list of path components.\n   *\n   * @param unixPath the Unix-style path\n   * @return a list of path components\n   */\n  public static List<String> parse(String unixPath) {\n    List<String> pathComponents = new ArrayList<>();\n    // -1 limit for Guava Splitter behavior: https://errorprone.info/bugpattern/StringSplitter\n    for (String component : unixPath.split(\"/\", -1)) {\n      if (!component.isEmpty()) {\n        pathComponents.add(component);\n      }\n    }\n    return pathComponents;\n  }\n\n  private UnixPathParser() {}\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/AbsoluteUnixPathTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Assume;\nimport org.junit.Test;\n\n/** Test for {@link AbsoluteUnixPath}. */\npublic class AbsoluteUnixPathTest {\n\n  @Test\n  public void testGet_notAbsolute() {\n    try {\n      AbsoluteUnixPath.get(\"not/absolute\");\n      Assert.fail();\n\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\n          \"Path does not start with forward slash (/): not/absolute\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testFromPath() {\n    Assert.assertEquals(\n        \"/absolute/path\", AbsoluteUnixPath.fromPath(Paths.get(\"/absolute/path\")).toString());\n  }\n\n  @Test\n  public void testFromPath_windows() {\n    Assume.assumeTrue(System.getProperty(\"os.name\").startsWith(\"Windows\"));\n\n    Assert.assertEquals(\n        \"/absolute/path\", AbsoluteUnixPath.fromPath(Paths.get(\"T:\\\\absolute\\\\path\")).toString());\n  }\n\n  @Test\n  public void testEquals() {\n    AbsoluteUnixPath absoluteUnixPath1 = AbsoluteUnixPath.get(\"/absolute/path\");\n    AbsoluteUnixPath absoluteUnixPath2 = AbsoluteUnixPath.get(\"/absolute/path/\");\n    AbsoluteUnixPath absoluteUnixPath3 = AbsoluteUnixPath.get(\"/another/path\");\n    Assert.assertEquals(absoluteUnixPath1, absoluteUnixPath2);\n    Assert.assertNotEquals(absoluteUnixPath1, absoluteUnixPath3);\n  }\n\n  @Test\n  public void testResolve_relativeUnixPath() {\n    AbsoluteUnixPath absoluteUnixPath1 = AbsoluteUnixPath.get(\"/\");\n    Assert.assertEquals(absoluteUnixPath1, absoluteUnixPath1.resolve(\"\"));\n    Assert.assertEquals(\"/file\", absoluteUnixPath1.resolve(\"file\").toString());\n    Assert.assertEquals(\"/relative/path\", absoluteUnixPath1.resolve(\"relative/path\").toString());\n\n    AbsoluteUnixPath absoluteUnixPath2 = AbsoluteUnixPath.get(\"/some/path\");\n    Assert.assertEquals(absoluteUnixPath2, absoluteUnixPath2.resolve(\"\"));\n    Assert.assertEquals(\"/some/path/file\", absoluteUnixPath2.resolve(\"file\").toString());\n    Assert.assertEquals(\n        \"/some/path/relative/path\", absoluteUnixPath2.resolve(\"relative/path\").toString());\n  }\n\n  @Test\n  public void testResolve_Path_notRelative() {\n    AbsoluteUnixPath absoluteUnixPath = AbsoluteUnixPath.get(\"/\");\n    Path path = Paths.get(\"/not/relative\");\n    IllegalArgumentException exception =\n        Assert.assertThrows(IllegalArgumentException.class, () -> absoluteUnixPath.resolve(path));\n    Assert.assertEquals(\"Cannot resolve against absolute Path: \" + path, exception.getMessage());\n  }\n\n  @Test\n  public void testResolve_Path() {\n    AbsoluteUnixPath absoluteUnixPath1 = AbsoluteUnixPath.get(\"/\");\n    Assert.assertEquals(absoluteUnixPath1, absoluteUnixPath1.resolve(Paths.get(\"\")));\n    Assert.assertEquals(\"/file\", absoluteUnixPath1.resolve(Paths.get(\"file\")).toString());\n    Assert.assertEquals(\n        \"/relative/path\", absoluteUnixPath1.resolve(Paths.get(\"relative/path\")).toString());\n\n    AbsoluteUnixPath absoluteUnixPath2 = AbsoluteUnixPath.get(\"/some/path\");\n    Assert.assertEquals(absoluteUnixPath2, absoluteUnixPath2.resolve(Paths.get(\"\")));\n    Assert.assertEquals(\n        \"/some/path/file\", absoluteUnixPath2.resolve(Paths.get(\"file///\")).toString());\n    Assert.assertEquals(\n        \"/some/path/relative/path\",\n        absoluteUnixPath2.resolve(Paths.get(\"relative//path/\")).toString());\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/ContainerBuildPlanTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ContainerBuildPlanTest}. */\npublic class ContainerBuildPlanTest {\n\n  @Test\n  public void testDefaults() {\n    ContainerBuildPlan plan = ContainerBuildPlan.builder().build();\n\n    Assert.assertEquals(\"scratch\", plan.getBaseImage());\n    Assert.assertEquals(ImmutableSet.of(new Platform(\"amd64\", \"linux\")), plan.getPlatforms());\n    Assert.assertEquals(ImageFormat.Docker, plan.getFormat());\n    Assert.assertEquals(Instant.EPOCH, plan.getCreationTime());\n    Assert.assertEquals(Collections.emptyMap(), plan.getEnvironment());\n    Assert.assertEquals(Collections.emptySet(), plan.getVolumes());\n    Assert.assertEquals(Collections.emptyMap(), plan.getLabels());\n    Assert.assertEquals(Collections.emptySet(), plan.getExposedPorts());\n    Assert.assertNull(plan.getUser());\n    Assert.assertNull(plan.getWorkingDirectory());\n    Assert.assertNull(plan.getEntrypoint());\n    Assert.assertNull(plan.getCmd());\n    Assert.assertEquals(Collections.emptyList(), plan.getLayers());\n  }\n\n  @Test\n  public void testBuilder() {\n    ContainerBuildPlan plan = createSamplePlan();\n\n    Assert.assertEquals(\"base/image\", plan.getBaseImage());\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"testOs\", \"testArchitecture\")), plan.getPlatforms());\n    Assert.assertEquals(ImageFormat.OCI, plan.getFormat());\n    Assert.assertEquals(Instant.ofEpochMilli(30), plan.getCreationTime());\n    Assert.assertEquals(ImmutableMap.of(\"env\", \"var\"), plan.getEnvironment());\n    Assert.assertEquals(\n        ImmutableSet.of(AbsoluteUnixPath.get(\"/mnt/foo\"), AbsoluteUnixPath.get(\"/bar\")),\n        plan.getVolumes());\n    Assert.assertEquals(ImmutableMap.of(\"com.example.label\", \"cool\"), plan.getLabels());\n    Assert.assertEquals(ImmutableSet.of(Port.tcp(443)), plan.getExposedPorts());\n    Assert.assertEquals(\":\", plan.getUser());\n    Assert.assertEquals(AbsoluteUnixPath.get(\"/workspace\"), plan.getWorkingDirectory());\n    Assert.assertEquals(Arrays.asList(\"foo\", \"entrypoint\"), plan.getEntrypoint());\n    Assert.assertEquals(Arrays.asList(\"bar\", \"cmd\"), plan.getCmd());\n\n    Assert.assertEquals(1, plan.getLayers().size());\n    MatcherAssert.assertThat(\n        plan.getLayers().get(0), CoreMatchers.instanceOf(FileEntriesLayer.class));\n    Assert.assertEquals(\n        Arrays.asList(\n            new FileEntry(\n                Paths.get(\"/src/file/foo\"),\n                AbsoluteUnixPath.get(\"/path/in/container\"),\n                FilePermissions.fromOctalString(\"644\"),\n                Instant.ofEpochSecond(1))),\n        ((FileEntriesLayer) plan.getLayers().get(0)).getEntries());\n  }\n\n  @Test\n  public void testToBuilder() {\n    ContainerBuildPlan plan = createSamplePlan().toBuilder().build();\n\n    Assert.assertEquals(\"base/image\", plan.getBaseImage());\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"testOs\", \"testArchitecture\")), plan.getPlatforms());\n    Assert.assertEquals(ImageFormat.OCI, plan.getFormat());\n    Assert.assertEquals(Instant.ofEpochMilli(30), plan.getCreationTime());\n    Assert.assertEquals(ImmutableMap.of(\"env\", \"var\"), plan.getEnvironment());\n    Assert.assertEquals(\n        ImmutableSet.of(AbsoluteUnixPath.get(\"/mnt/foo\"), AbsoluteUnixPath.get(\"/bar\")),\n        plan.getVolumes());\n    Assert.assertEquals(ImmutableMap.of(\"com.example.label\", \"cool\"), plan.getLabels());\n    Assert.assertEquals(ImmutableSet.of(Port.tcp(443)), plan.getExposedPorts());\n    Assert.assertEquals(\":\", plan.getUser());\n    Assert.assertEquals(AbsoluteUnixPath.get(\"/workspace\"), plan.getWorkingDirectory());\n    Assert.assertEquals(Arrays.asList(\"foo\", \"entrypoint\"), plan.getEntrypoint());\n    Assert.assertEquals(Arrays.asList(\"bar\", \"cmd\"), plan.getCmd());\n\n    Assert.assertEquals(1, plan.getLayers().size());\n    MatcherAssert.assertThat(\n        plan.getLayers().get(0), CoreMatchers.instanceOf(FileEntriesLayer.class));\n    Assert.assertEquals(\n        Arrays.asList(\n            new FileEntry(\n                Paths.get(\"/src/file/foo\"),\n                AbsoluteUnixPath.get(\"/path/in/container\"),\n                FilePermissions.fromOctalString(\"644\"),\n                Instant.ofEpochSecond(1))),\n        ((FileEntriesLayer) plan.getLayers().get(0)).getEntries());\n  }\n\n  @Test\n  public void testAddPlatform_duplicatePlatforms() {\n    ContainerBuildPlan plan =\n        ContainerBuildPlan.builder()\n            .addPlatform(\"testOS\", \"testArchitecture\")\n            .addPlatform(\"testOS\", \"testArchitecture\")\n            .build();\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"amd64\", \"linux\"), new Platform(\"testOS\", \"testArchitecture\")),\n        plan.getPlatforms());\n  }\n\n  @Test\n  public void testSetPlatforms_emptyPlatformsSet() {\n    try {\n      ContainerBuildPlan.builder().setPlatforms(Collections.emptySet());\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"platforms set cannot be empty\", ex.getMessage());\n    }\n  }\n\n  private ContainerBuildPlan createSamplePlan() {\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .addEntry(Paths.get(\"/src/file/foo\"), AbsoluteUnixPath.get(\"/path/in/container\"))\n            .build();\n\n    return ContainerBuildPlan.builder()\n        .setBaseImage(\"base/image\")\n        .setPlatforms(ImmutableSet.of(new Platform(\"testOs\", \"testArchitecture\")))\n        .setFormat(ImageFormat.OCI)\n        .setCreationTime(Instant.ofEpochMilli(30))\n        .setEnvironment(ImmutableMap.of(\"env\", \"var\"))\n        .setVolumes(ImmutableSet.of(AbsoluteUnixPath.get(\"/mnt/foo\"), AbsoluteUnixPath.get(\"/bar\")))\n        .setLabels(ImmutableMap.of(\"com.example.label\", \"cool\"))\n        .setExposedPorts(ImmutableSet.of(Port.tcp(443)))\n        .setLayers(Arrays.asList(layer))\n        .setUser(\":\")\n        .setWorkingDirectory(AbsoluteUnixPath.get(\"/workspace\"))\n        .setEntrypoint(Arrays.asList(\"foo\", \"entrypoint\"))\n        .setCmd(Arrays.asList(\"bar\", \"cmd\"))\n        .build();\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/FileEntriesLayerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link FileEntriesLayer}. */\npublic class FileEntriesLayerTest {\n\n  private static FileEntry defaultFileEntry(Path source, AbsoluteUnixPath destination) {\n    return new FileEntry(\n        source,\n        destination,\n        FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination),\n        FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n  }\n\n  @Test\n  public void testAddEntryRecursive_defaults() throws IOException, URISyntaxException {\n    Path testDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI()).toAbsolutePath();\n    Path testFile = Paths.get(Resources.getResource(\"core/fileA\").toURI());\n\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .addEntryRecursive(testDirectory, AbsoluteUnixPath.get(\"/app/layer/\"))\n            .addEntryRecursive(testFile, AbsoluteUnixPath.get(\"/app/fileA\"))\n            .build();\n\n    ImmutableSet<FileEntry> expectedLayerEntries =\n        ImmutableSet.of(\n            defaultFileEntry(testDirectory, AbsoluteUnixPath.get(\"/app/layer/\")),\n            defaultFileEntry(testDirectory.resolve(\"a\"), AbsoluteUnixPath.get(\"/app/layer/a/\")),\n            defaultFileEntry(testDirectory.resolve(\"a/b\"), AbsoluteUnixPath.get(\"/app/layer/a/b/\")),\n            defaultFileEntry(\n                testDirectory.resolve(\"a/b/bar\"), AbsoluteUnixPath.get(\"/app/layer/a/b/bar/\")),\n            defaultFileEntry(testDirectory.resolve(\"c/\"), AbsoluteUnixPath.get(\"/app/layer/c\")),\n            defaultFileEntry(\n                testDirectory.resolve(\"c/cat/\"), AbsoluteUnixPath.get(\"/app/layer/c/cat\")),\n            defaultFileEntry(testDirectory.resolve(\"foo\"), AbsoluteUnixPath.get(\"/app/layer/foo\")),\n            defaultFileEntry(testFile, AbsoluteUnixPath.get(\"/app/fileA\")));\n\n    Assert.assertEquals(expectedLayerEntries, ImmutableSet.copyOf(layer.getEntries()));\n  }\n\n  @Test\n  public void testAddEntryRecursive_otherFileEntryProperties()\n      throws IOException, URISyntaxException {\n    Path testDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI()).toAbsolutePath();\n    Path testFile = Paths.get(Resources.getResource(\"core/fileA\").toURI());\n\n    FilePermissions permissions1 = FilePermissions.fromOctalString(\"111\");\n    FilePermissions permissions2 = FilePermissions.fromOctalString(\"777\");\n    Instant timestamp1 = Instant.ofEpochSecond(123);\n    Instant timestamp2 = Instant.ofEpochSecond(987);\n    String ownership1 = \"root\";\n    String ownership2 = \"nobody:65432\";\n\n    FilePermissionsProvider permissionsProvider =\n        (source, destination) ->\n            destination.toString().startsWith(\"/app/layer/a\") ? permissions1 : permissions2;\n    ModificationTimeProvider timestampProvider =\n        (source, destination) ->\n            destination.toString().startsWith(\"/app/layer/a\") ? timestamp1 : timestamp2;\n    OwnershipProvider ownershipProvider =\n        (source, destination) ->\n            destination.toString().startsWith(\"/app/layer/a\") ? ownership1 : ownership2;\n\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .addEntry(\n                new FileEntry(\n                    Paths.get(\"foo\"), AbsoluteUnixPath.get(\"/foo\"), permissions1, timestamp1))\n            .addEntryRecursive(\n                testDirectory,\n                AbsoluteUnixPath.get(\"/app/layer/\"),\n                permissionsProvider,\n                timestampProvider,\n                ownershipProvider)\n            .addEntryRecursive(\n                testFile,\n                AbsoluteUnixPath.get(\"/app/fileA\"),\n                permissionsProvider,\n                timestampProvider,\n                ownershipProvider)\n            .build();\n\n    ImmutableSet<FileEntry> expectedLayerEntries =\n        ImmutableSet.of(\n            new FileEntry(\n                Paths.get(\"foo\"), AbsoluteUnixPath.get(\"/foo\"), permissions1, timestamp1, \"\"),\n            new FileEntry(\n                testDirectory,\n                AbsoluteUnixPath.get(\"/app/layer/\"),\n                permissions2,\n                timestamp2,\n                ownership2),\n            new FileEntry(\n                testDirectory.resolve(\"a\"),\n                AbsoluteUnixPath.get(\"/app/layer/a/\"),\n                permissions1,\n                timestamp1,\n                ownership1),\n            new FileEntry(\n                testDirectory.resolve(\"a/b\"),\n                AbsoluteUnixPath.get(\"/app/layer/a/b/\"),\n                permissions1,\n                timestamp1,\n                ownership1),\n            new FileEntry(\n                testDirectory.resolve(\"a/b/bar\"),\n                AbsoluteUnixPath.get(\"/app/layer/a/b/bar/\"),\n                permissions1,\n                timestamp1,\n                ownership1),\n            new FileEntry(\n                testDirectory.resolve(\"c/\"),\n                AbsoluteUnixPath.get(\"/app/layer/c\"),\n                permissions2,\n                timestamp2,\n                ownership2),\n            new FileEntry(\n                testDirectory.resolve(\"c/cat/\"),\n                AbsoluteUnixPath.get(\"/app/layer/c/cat\"),\n                permissions2,\n                timestamp2,\n                ownership2),\n            new FileEntry(\n                testDirectory.resolve(\"foo\"),\n                AbsoluteUnixPath.get(\"/app/layer/foo\"),\n                permissions2,\n                timestamp2,\n                ownership2),\n            new FileEntry(\n                testFile,\n                AbsoluteUnixPath.get(\"/app/fileA\"),\n                permissions2,\n                timestamp2,\n                ownership2));\n\n    Assert.assertEquals(expectedLayerEntries, ImmutableSet.copyOf(layer.getEntries()));\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/FileEntryTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class FileEntryTest {\n\n  @Test\n  public void testToString() {\n    Assert.assertEquals(\n        \"{a\" + File.separator + \"path,/an/absolute/unix/path,333,1970-01-01T00:00:00Z,0:0}\",\n        new FileEntry(\n                Paths.get(\"a/path\"),\n                AbsoluteUnixPath.get(\"/an/absolute/unix/path\"),\n                FilePermissions.fromOctalString(\"333\"),\n                Instant.EPOCH,\n                \"0:0\")\n            .toString());\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/FilePermissionsTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport java.nio.file.attribute.PosixFilePermission;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link FilePermissions}. */\npublic class FilePermissionsTest {\n\n  @Test\n  public void testFromOctalString() {\n    Assert.assertEquals(new FilePermissions(0777), FilePermissions.fromOctalString(\"777\"));\n    Assert.assertEquals(new FilePermissions(0000), FilePermissions.fromOctalString(\"000\"));\n    Assert.assertEquals(new FilePermissions(0123), FilePermissions.fromOctalString(\"123\"));\n    Assert.assertEquals(new FilePermissions(0755), FilePermissions.fromOctalString(\"755\"));\n    Assert.assertEquals(new FilePermissions(0644), FilePermissions.fromOctalString(\"644\"));\n\n    ImmutableList<String> badStrings = ImmutableList.of(\"abc\", \"-123\", \"777444333\", \"987\", \"3\");\n    for (String badString : badStrings) {\n      try {\n        FilePermissions.fromOctalString(badString);\n        Assert.fail();\n      } catch (IllegalArgumentException ex) {\n        Assert.assertEquals(\n            \"octalPermissions must be a 3-digit octal number (000-777)\", ex.getMessage());\n      }\n    }\n  }\n\n  @Test\n  public void testFromPosixFilePermissions() {\n    Assert.assertEquals(\n        new FilePermissions(0000), FilePermissions.fromPosixFilePermissions(ImmutableSet.of()));\n    Assert.assertEquals(\n        new FilePermissions(0110),\n        FilePermissions.fromPosixFilePermissions(\n            ImmutableSet.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_EXECUTE)));\n    Assert.assertEquals(\n        new FilePermissions(0202),\n        FilePermissions.fromPosixFilePermissions(\n            ImmutableSet.of(PosixFilePermission.OWNER_WRITE, PosixFilePermission.OTHERS_WRITE)));\n    Assert.assertEquals(\n        new FilePermissions(0044),\n        FilePermissions.fromPosixFilePermissions(\n            ImmutableSet.of(PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ)));\n    Assert.assertEquals(\n        new FilePermissions(0777),\n        FilePermissions.fromPosixFilePermissions(\n            ImmutableSet.copyOf(PosixFilePermission.values())));\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/PortTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Port}. */\npublic class PortTest {\n\n  @Test\n  public void testTcp() {\n    Port port = Port.tcp(5555);\n    Assert.assertEquals(5555, port.getPort());\n    Assert.assertEquals(\"5555/tcp\", port.toString());\n  }\n\n  @Test\n  public void testUdp() {\n    Port port = Port.udp(6666);\n    Assert.assertEquals(6666, port.getPort());\n    Assert.assertEquals(\"6666/udp\", port.toString());\n  }\n\n  @Test\n  public void testParseProtocol() {\n    Assert.assertEquals(Port.tcp(1111), Port.parseProtocol(1111, \"tcp\"));\n    Assert.assertEquals(Port.udp(2222), Port.parseProtocol(2222, \"udp\"));\n    Assert.assertEquals(Port.tcp(3333), Port.parseProtocol(3333, \"\"));\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/api/buildplan/RelativeUnixPathTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api.buildplan;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link RelativeUnixPath}. */\npublic class RelativeUnixPathTest {\n\n  @Test\n  public void testGet_absolute() {\n    try {\n      RelativeUnixPath.get(\"/absolute\");\n      Assert.fail();\n\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"Path starts with forward slash (/): /absolute\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testGet() {\n    Assert.assertEquals(\n        ImmutableList.of(\"some\", \"relative\", \"path\"),\n        RelativeUnixPath.get(\"some/relative///path\").getRelativePathComponents());\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/java/com/google/cloud/tools/jib/buildplan/UnixPathParserTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.buildplan;\n\nimport com.google.common.collect.ImmutableList;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link UnixPathParser}. */\npublic class UnixPathParserTest {\n\n  @Test\n  public void testParse() {\n    Assert.assertEquals(ImmutableList.of(\"some\", \"path\"), UnixPathParser.parse(\"/some/path\"));\n    Assert.assertEquals(ImmutableList.of(\"some\", \"path\"), UnixPathParser.parse(\"some/path/\"));\n    Assert.assertEquals(ImmutableList.of(\"some\", \"path\"), UnixPathParser.parse(\"some///path///\"));\n    // Windows-style paths are resolved in Unix semantics.\n    Assert.assertEquals(\n        ImmutableList.of(\"\\\\windows\\\\path\"), UnixPathParser.parse(\"\\\\windows\\\\path\"));\n    Assert.assertEquals(ImmutableList.of(\"T:\\\\dir\"), UnixPathParser.parse(\"T:\\\\dir\"));\n    Assert.assertEquals(\n        ImmutableList.of(\"T:\\\\dir\", \"real\", \"path\"), UnixPathParser.parse(\"T:\\\\dir/real/path\"));\n  }\n}\n"
  },
  {
    "path": "jib-build-plan/src/test/resources/core/fileA",
    "content": "Crepe cakes are good."
  },
  {
    "path": "jib-build-plan/src/test/resources/core/layer/a/b/bar",
    "content": "bar\n"
  },
  {
    "path": "jib-build-plan/src/test/resources/core/layer/c/cat",
    "content": "cat\n"
  },
  {
    "path": "jib-build-plan/src/test/resources/core/layer/foo",
    "content": "foo\n"
  },
  {
    "path": "jib-cli/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n## 0.13.0\n\n### Added\n\n### Changed\n\n### Fixed\n- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171))\n- fix: (WAR Containerization) modify default entrypoint to `java -jar /usr/local/jetty/start.jar --module=ee10-deploy` for Jetty 12+ compatibility ([#4216](https://github.com/GoogleContainerTools/jib/pull/4216))\n\n## 0.12.0\n\n### Changed\n- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745))\n- Re-synchronized jackson dependencies with BOM to use latest versions ([#3768](https://github.com/GoogleContainerTools/jib/pull/3768))\n\n## 0.11.0\n\n### Added\n- Included `imagePushed` field to image metadata json output file which provides information on whether an image was pushed by Jib. ([#3641](https://github.com/GoogleContainerTools/jib/pull/3641))\n- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)).\n- Starting with jib-cli 0.11.0, [SLSA 3 signatures](https://slsa.dev/) will be generated with every release. ([#3762](https://github.com/GoogleContainerTools/jib/pull/3726)).\n\n### Changed\n- Upgraded slf4j-api to 2.0.0 ([#3735](https://github.com/GoogleContainerTools/jib/pull/3735)).\n- Upgraded nullaway to 0.9.9 ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720)).\n\nThanks to our community contributors @wwadge @oliver-brm and @laurentsimon!\n\n## 0.10.0\n\n### Changed\n- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/issues/3612)).\n\n### Fixed\n\n- Incorrect release sha256 file for jib-cli. ([#3584](https://github.com/GoogleContainerTools/jib/issues/3584))\n\n## 0.9.0\n\n### Changed\n\n- For Java 17, changed the default base image of the Jib CLI `jar` command from the `azul/zulu-openjdk` to [`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin). ([#3483](https://github.com/GoogleContainerTools/jib/issues/3483))\n\n## 0.8.0\n\n### Added\n\n- Increased robustness in registry communications by retrying HTTP requests (to the effect of retrying image pushes or pulls) on I/O exceptions with exponential backoffs. ([#3351](https://github.com/GoogleContainerTools/jib/pull/3351))\n- Now also supports `username` and `password` properties for the `auths` section in a Docker config (`~/.docker/config.json`). (Previously, only supported was a base64-encoded username and password string of the `auth` property.) ([#3365](https://github.com/GoogleContainerTools/jib/pull/3365))\n\n### Changed\n\n- Downgraded Google HTTP libraries to 1.34.0 to resolve network issues. ([#3415](https://github.com/GoogleContainerTools/jib/pull/3415), [#3058](https://github.com/GoogleContainerTools/jib/issues/3058), [#3409](https://github.com/GoogleContainerTools/jib/issues/3409))\n- Changed the default base image of the Jib CLI `jar` command from the `adoptopenjdk` images to the [`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin) (for Java 8 and 11) and [`azul/zulu-openjdk`](https://hub.docker.com/r/azul/zulu-openjdk) (for Java 17) images on Docker Hub. Note that Temurin (by Adoptium) is the new name of AdoptOpenJDK. ([#3491](https://github.com/GoogleContainerTools/jib/pull/3491))\n\n## 0.7.0\n\n### Added\n\n- Added the `war` command which can be used to containerize a standard WAR with `$ jib war --target ... my-app.war`. The command will explode out the contents of the WAR into optimized layers on the container. ([#3285](https://github.com/GoogleContainerTools/jib/pull/3285))\n\n## 0.6.0\n\n### Added\n\n- Added automatic update check. Jib CLI will now display a message if a new version is available. See the [privacy page](https://github.com/GoogleContainerTools/jib/blob/master/docs/privacy.md) for more details. ([#3165](https://github.com/GoogleContainerTools/jib/pull/3165))\n- Added `--image-metadata-out` option to specify JSON output file that should contain image metadata (image ID, digest, and tags) after build is complete. ([#3187](https://github.com/GoogleContainerTools/jib/pull/3187))\n\n## 0.5.0\n\n### Fixed\n\n- Fixed an issue where critical error messages (for example, unauthorized access from a registry) were erased by progress reporting and not shown. ([#3148](https://github.com/GoogleContainerTools/jib/issues/3148))\n\n## 0.4.0\n\n### Added\n\n- Added support for [configuring registry mirrors](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#i-am-hitting-docker-hub-rate-limits-how-can-i-configure-registry-mirrors) for base images. This is useful when hitting [Docker Hub rate limits](https://www.docker.com/increase-rate-limits). Only public mirrors (such as `mirror.gcr.io`) are supported. ([#3134](https://github.com/GoogleContainerTools/jib/pull/3134))\n\n## 0.3.0\n\n### Changed\n\n- Changed the default base image of the Jib CLI jar command from the `openjdk` images to the `adoptopenjdk` images on Docker Hub. ([#3108](https://github.com/GoogleContainerTools/jib/pull/3108))\n\n## 0.2.0\n\n### Added\n\n- Added the `jar` command which can be used to containerize a JAR with `$ jib jar --target ... my-app.jar`. By default, the command will add the contents of the JAR into optimized layers on the container. ([#11](https://github.com/GoogleContainerTools/jib/projects/11))\n"
  },
  {
    "path": "jib-cli/README.md",
    "content": "# Jib CLI\n\n<img src=\"https://img.shields.io/badge/status-preview-orange\">\n\n[![Chocolatey](https://img.shields.io/chocolatey/v/jib.svg)](https://chocolatey.org/packages/jib)\n[![Chocolatey](https://img.shields.io/chocolatey/dt/jib.svg)](https://chocolatey.org/packages/jib)\n[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)\n\n`jib` is a general-purpose command-line utility for building Docker or [OCI](https://github.com/opencontainers/image-spec) container images from file system content as well as JAR files. Jib CLI builds containers [fast and reproducibly without Docker](https://github.com/GoogleContainerTools/jib#goals) like [other Jib tools](https://github.com/GoogleContainerTools/jib#what-is-jib).\n\n```sh\n# docker not required\n$ docker\n-bash: docker: command not found\n# build and upload an image\n$ jib build --target=my-registry.example.com/built-by-jib\n```\n\nAdditionally, Jib CLI can directly build an optimized image for JAR files (including Spring Boot fat JAR).\n```sh\n$ jib jar --target=my-registry.example.com/jar-app myapp.jar\n```\n\nThe CLI tool is powered by [Jib Core](https://github.com/GoogleContainerTools/jib/tree/master/jib-core), a Java library for building containers without Docker.\n\n## Table of Contents\n* [Get the Jib CLI](#get-the-jib-cli)\n  * [Download a Java Application](#download-a-java-application)\n  * [Windows: Install with `choco`](#windows-install-with-choco)\n  * [Build Yourself from Source (for Advanced Users)](#build-yourself-from-source-for-advanced-users)\n* [Supported Commands](#supported-commands)\n* [Build Command](#build-command)\n  * [Quickstart](#quickstart)\n  * [Options](#options)\n* [Jar Command](#jar-command)\n  * [Quickstart](#quickstart-1)\n  * [Options](#options-1)\n* [War Command](#war-command)\n  * [Quickstart](#quickstart-2)\n  * [Options](#options-2)\n* [Options Shared Between Jar and War Commands](#options-shared-between-jar-and-war-commands)\n* [Common Jib CLI Options](#common-jib-cli-options)\n  * [Auth/Security](#authsecurity)\n  * [Info Params](#info-params)\n  * [Debugging Params](#debugging-params)\n* [Global Jib Configuration](#global-jib-configuration)\n* [References](#references)\n  * [Fully Annotated Build File (`jib.yaml`)](#fully-annotated-build-file-jibyaml)\n* [Privacy](#privacy)\n\n## Get the Jib CLI\n\nMost users should download a ZIP archive (Java application). We are working on releasing a native executable binary using GraalVM. (Help wanted!)\n\n### Download a Java Application\n\nA JRE is required to run this Jib CLI distribution.\n\nFind the [latest jib-cli 0.13.0 release](https://github.com/GoogleContainerTools/jib/releases/latest) on the [Releases page](https://github.com/GoogleContainerTools/jib/releases) and download `jib-jre-<version>.zip`.\n\nUnzip the zip file. The zip file contains the `jib` (`jib.bat` for Windows) script at `jib/bin/`. Optionally, add the binary directory to your `$PATH` so that you can call `jib` from anywhere.\n\nWe generate [SLSA3 signatures](https://slsa.dev/) using the OpenSSF's [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) during the release process. To verify a release binary:\n1. Install the verification tool from [slsa-framework/slsa-verifier#installation](https://github.com/slsa-framework/slsa-verifier#installation).\n2. Download the signature file `jib-jre-<version>.zip.intoto.jsonl` from the [GitHub releases page](https://github.com/GoogleContainerTools/jib/releases/latest).\n3. Run the verifier:\n```shell\nslsa-verifier -artifact-path jib-jre-<version>.zip -provenance jib-jre-<version>.zip.intoto.jsonl -source github.com/GoogleContainerTools/jib -branch master -workflow-input release_version=<version>\n```\n\n### Windows: Install with `choco`\n\nOn Windows, you can use the [`choco`](https://community.chocolatey.org/packages/jib) command. To install, upgrade, or uninstall Jib CLI, run the following commands from the command-line or PowerShell:\n```\nchoco install jib\nchoco upgrade jib\nchoco uninstall jib\n```\n\n### Build Yourself from Source (for Advanced Users)\n\nUse the `application` plugin's `installDist` task to create a runnable installation in\n`build/install/jib`.  A zip and tar file are also created in `build/distributions`.\n```sh\n# build\n$ ./gradlew jib-cli:installDist\n# run\n$ ./jib-cli/build/install/jib/bin/jib\n```\n\n## Supported Commands\n\nThe Jib CLI supports two commands:\n 1. `build` - containerizes using a [build file](#fully-annotated-build-file-jibyaml).\n 2. `jar` - containerizes JAR files.\n 3. `war` - containerizes WAR files.\n\n## Build Command\n\nThis command follows the following pattern:\n```\njib build --target <image name> [options]\n```\n\n### Quickstart\n\n1. Create a hello world script (`script.sh`) containing:\n    ```sh\n    #!/bin/sh\n    echo \"Hello World\"\n    ```\n2. Create a [build file](#fully-annotated-build-file-jibyaml). The default is a file named `jib.yaml` in the project root.\n    ```yaml\n    apiVersion: jib/v1alpha1\n    kind: BuildFile\n    \n    from:\n      image: ubuntu\n    \n    entrypoint: [\"/script.sh\"]\n    \n    layers:\n      entries:\n        - name: scripts\n          files:\n            - properties:\n                filePermissions: 755\n              src: script.sh\n              dest: /script.sh\n    ```\n3. Build to docker daemon\n   ```\n    $ jib build --target=docker://jib-cli-quickstart\n   ```\n4. Run the container\n   ```\n    $ docker run jib-cli-quickstart\n    Hello World\n   ```\n\n### Options\n\nOptional flags for the `build` command:\n\nOption | Description\n---     | ---\n`-b, --build-file` |  The path to the build file (ex: path/to/other-jib.yaml)\n`-c, --context`    |  The context root directory of the build (ex: path/to/my/build/things)\n`-p, --parameter`  |  Templating parameter to inject into build file, replace ${<name>} with <value> (repeatable)\n\n## Jar Command\n\nThis command follows the following pattern:\n```\njib jar --target <image name> path/to/myapp.jar [options]\n```\n\n### Quickstart\n\n1. Have your JAR (thin or fat) ready. We will be using the [Spring Petclinic](https://projects.spring.io/spring-petclinic/) JAR in this Quickstart.\n   ```\n    $ git clone https://github.com/spring-projects/spring-petclinic.git\n    $ cd spring-petclinic\n    $ ./mvnw package\n   ```\n2. Containerize your JAR using the `jar` command. In the default mode (exploded), the entrypoint will be set to `java -cp /app/dependencies/:/app/explodedJar/ HelloWorld`\n   ```\n    $ jib jar --target=docker://cli-jar-quickstart target/spring-petclinic-*.jar\n   ```\n3. Run the image and open your browser at http://localhost:8080\n   ```\n    $ docker run -p 8080:8080 cli-jar-quickstart\n   ```\n\n### Options\n\nOptional flags for the `jar` command:\n\nOption | Description\n---       | ---\n`--jvm-flags`     | JVM arguments, example: `--jvm-flags=-Dmy.property=value,-Xshare:off`\n`--mode`          | The jar processing mode, candidates: exploded, packaged, default: exploded\n\n## War Command\n\nThis command follows the following pattern:\n```\n $ jib war --target <image-name> path/to/myapp.war \n```\n\n## Quickstart\n\n1. Have your sample WAR ready and use the `war` command to containerize your WAR. By default, the WAR command uses [`jetty`](https://hub.docker.com/_/jetty) as the base image so the entrypoint is set to `java -jar /usr/local/jetty/start.jar --module=ee10-deploy`:\n    ```\n     $ jib war --target=docker://cli-war-quickstart <your-sample>.war\n    ```\n2.  Run the image and open your browser at http://localhost:8080\n    ```\n     $ docker run -p 8080:8080 cli-war-quickstart\n    ```\n## Options\n\nFlags for the `war` command:\n\nOption | Description\n---       | ---\n`--app-root` | The app root on the container. Customizing the app-root is helpful if you are using a different Servlet engine base image (for example, Tomcat)\n\n## Options Shared Between the Jar and War Commands\nHere are a few container configurations that can be customized when using the `jar` and `war` commands.\n\nOption | Description\n---       | ---\n`--creation-time` | The creation time of the container in milliseconds since epoch or iso8601 format. Overrides the default (1970-01-01T00:00:00Z)\n`--entrypoint`    | Entrypoint for container. Overrides the default entrypoint, example: `--entrypoint='custom entrypoint'`\n`--environment-variables`  | Environment variables to write into container, example: `--environment-variables env1=env_value1, env2=env_value2`.\n`--expose`        | Ports to expose on container, example: `--expose=5000,7/udp`.\n`--from`          | The base image to use.\n`--image-format`  | Format of container, candidates: Docker, OCI, default: Docker.\n`--labels`        | Labels to write into container metadata, example: `--labels=label1=value1,label2=value2`.\n`--program-args`  | Program arguments for container entrypoint.\n`-u, --user`      | The user to run the container as, example: `--user=myuser:mygroup`.\n`--volumes`       | Directories on container to hold extra volumes, example: `--volumes=/var/log,/var/log2`.\n\n## Common Jib CLI Options\n\nThe options can either be specified in the command line or defined in a configuration file:\n```\n[@<filename>...]      One or more argument files containing options.\n```\n \n### Auth/Security\n\n```\n    --allow-insecure-registries            Allow jib to send credentials over http (insecure)\n    --send-credentials-over-http           Allow jib to send credentials over http (very insecure)\n```\n\n### Registry Credentials\n\nCredentials can be specified using credential helpers or username + password. The following options are available:\n\n```\n    --credential-helper <credHelper>      credential helper for communicating with both target and base image registries, either a path to the helper, or a suffix for an executable named `docker-credential-<suffix>`\n    --to-credential-helper <credHelper>   credential helper for communicating with target registry, either a path to the helper, or a suffix for an executable named `docker-credential-<suffix>\n    --from-credential-helper <credHelper> credential helper for communicating with base image registry, either a path to the helper, or a suffix for an executable named `docker-credential-<suffix>`\n\n    --username <username>                  username for communicating with both target and base image registries\n    --password <password>                  password for communicating with both target and base image registries\n    --to-username <username>               username for communicating with target image registry\n    --to-password <password>               password for communicating with target image registry\n    --from-username <username>             username for communicating with base image registry\n    --from-password <password>             password for communicating with base image registry\n```\n*Note* - Combinations of `credential-helper`, `username` and `password` flags come with restrictions and can be use only in the following ways:\n\nOnly Credential Helper\n1. `--credential-helper`\n2. `--to-credential-helper`\n3. `--from-credential-helper`\n4. `--to-credential-helper`, `--from-credential-helper`\n\nOnly Username and Password\n1. `--username`, `--password`\n2. `--to-username`, `--to-password`\n3. `--from-username`, `--from-password`\n4. `--to-username`, `--to-password`, `--from-username`, `--from-password`\n\nMixed Mode\n1. `--to-credential-helper`, `--from-username`, `--from-password`\n2. `--from-credential-helper`, `--to-username`, `--to-password`\n\n### Info Params\n\n```\n    --help                  print usage and exit\n    --console <type>        set console output type, candidates: auto, rich, plain, default: auto\n    --verbosity <level>     set logging verbosity, candidates: quiet, error, warn, lifecycle, info, debug, default: lifecycle\n-v, --version               Jib CLI version information\n```\n\n### Debugging Params\n\n```\n    --stacktrace            print stacktrace on error (for debugging issues in the jib-cli)\n    --http-trace            enable http tracing at level=config, output=console\n    --serialize             run jib in serialized mode\n```\n\n## Global Jib Configuration\n\nSome options can be set in the global Jib configuration file. The file is at the following locations on each platform:\n\n* *Linux: `[config root]/google-cloud-tools-java/jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/.config/` if not set)*\n* *Mac: `[config root]/Google/Jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/Library/Preferences/Config/` if not set)*\n* *Windows: `[config root]\\Google\\Jib\\Config\\config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`%LOCALAPPDATA%` if not set)*\n\n### Properties \n\n* `disableUpdateCheck`: when set to true, disables the periodic up-to-date version check.\n* `registryMirrors`: a list of mirror settings for each base image registry. In the following example, if the base image configured in Jib is for a Docker Hub image, then `mirror.gcr.io`, `localhost:5000`, and the Docker Hub (`registry-1.docker.io`) are tried in order until Jib can successfully pull a base image.\n\n```json\n{\n  \"disableUpdateCheck\": false,\n  \"registryMirrors\": [\n    {\n      \"registry\": \"registry-1.docker.io\",\n      \"mirrors\": [\"mirror.gcr.io\", \"localhost:5000\"]\n    },\n    {\n      \"registry\": \"quay.io\",\n      \"mirrors\": [\"private-mirror.test.com\"]\n    }\n  ]\n}\n```\n**Note about `mirror.gcr.io`**: it is _not_ a Docker Hub mirror but a cache. It caches [frequently-accessed public Docker Hub images](https://cloud.google.com/container-registry/docs/pulling-cached-images), and it's often possible that your base image does not exist in `mirror.gcr.io`. In that case, Jib will have to fall back to use Docker Hub.\n\n## References\n\n### Fully Annotated Build File (`jib.yaml`)\n\n```yaml\n# required apiVersion and kind, for compatibility over versions of the cli\napiVersion: jib/v1alpha1\nkind: BuildFile\n\n# full base image specification with detail for manifest lists or multiple architectures\nfrom:\n  image: \"ubuntu\"\n  # set platforms for multi architecture builds, defaults to `linux/amd64`\n  platforms:\n    - architecture: \"arm\"\n      os: \"linux\"\n    - architecture: \"amd64\"\n      os: \"darwin\"\n\n# creation time sets the creation time of the container only\n# can be: millis since epoch (ex: 1000) or an ISO 8601 creation time (ex: 2020-06-08T14:54:36+00:00)\ncreationTime: 2000\n\nformat: Docker # Docker or OCI\n\n# container environment variables\nenvironment:\n  \"KEY1\": \"v1\"\n  \"KEY2\": \"v2\"\n  \n# container labels\nlabels:\n  \"label1\": \"l1\"\n  \"label2\": \"l2\"\n  \n# specify volume mount points\nvolumes:\n  - \"/volume1\"\n  - \"/volume2\"\n\n# specify exposed ports metadata (port-number/protocol)\nexposedPorts:\n  - \"123/udp\"\n  - \"456\"      # default protocol is tcp\n  - \"789/tcp\"\n\n# the user to run the container (does not affect file permissions)\nuser: \"customUser\"\n\nworkingDirectory: \"/home\"\n\nentrypoint:\n  - \"sh\"\n  - \"script.sh\"\ncmd:\n  - \"--param\"\n  - \"param\"\n\n# file layers of the container\nlayers: \n  properties:                        # file properties applied to all layers\n    filePermissions: \"123\"           # octal file permissions, default is 644\n    directoryPermissions: \"123\"      # octal directory permissions, default is 755\n    user: \"2\"                        # default user is 0\n    group: \"4\"                       # default group is 0\n    timestamp: \"1232\"                # timestamp can be millis since epoch or ISO 8601 format, default is \"Epoch + 1 second\"\n  entries:\n    - name: \"scripts\"                # first layer\n      properties:                    # file properties applied to only this layer\n        filePermissions: \"123\"           \n        # see above for full list of properties...\n      files:                         # a list of copy directives constitute a single layer\n        - src: \"project/run.sh\"      # a simple copy directive (inherits layer level file properties)\n          dest: \"/home/run.sh\"       # all 'dest' specifications must be absolute paths on the container\n        - src: \"scripts\"             # a second copy directive in the same layer\n          dest: \"/home/scripts\"\n          excludes:                  # exclude all files matching these patterns\n            - \"**/exclude.me\"\n            - \"**/*.ignore\"\n          includes:                  # include only files matching these patterns\n            - \"**/include.me\"            \n          properties:                # file properties applied to only this copy directive\n            filePermissions: \"123\"           \n            # see above for full list of properties...  \n    - name: \"images\"                 # second layer, inherits file properties from global\n      files:\n        - src: \"images\"\n          dest: \"/images\"            \n```\n\n#### Layers Behavior\n\n- Copy directives are bound by the following rules\n  `src`: filetype determined by type on local disk\n   - if `src` is directory, `dest` is always considered a directory, directory and contents will be copied over and renamed to `dest`\n   - if `src` is file\n     - if `dest` ends with `/` then it is considered a target directory, file will be copied into directory\n     - if `dest` doesn't end with `/` then is is the target file location, `src` file will be copied and renamed to `dest`\n- Permissions for a file or directory that appear in multiple layers will prioritize the *last* layer and copy directive the file appears in. In the following example, `file.txt` as seen on the running container will have filePermissions `234`.\n    ```\n    - name: layer1\n      properties:\n        filePermissions: \"123\"\n      - src: file.txt\n        dest: /file.txt\n    - name: layer2\n      properties:\n        filePermissions: \"234\"\n      - src: file.txt\n        dest: /file.txt\n     ```\n- Parent directories that are not explicitly defined in a layer will the default properties in jib-core (permissions: 755, modification-time: epoch+1). In the following example, `/somewhere` on the container will have the directory permissions `755`, not `777` as some might expect.\n    ```\n    - name: layer\n      properties:\n        directoryPermissions: \"777\"\n      - src: file.txt\n        dest: /somewhere/file.txt\n    ```\n- `excludes` on a directory can lead to unintended inclusion of files in the directory, to exclude a directory *and* all its files\n     ```\n     excludes:\n       - \"**/exclude-dir\"\n       - \"**/exclude-dir/**\n     ```\n     \n#### Base Image Parameter Inheritance\n\nSome values defined in the base image may be preserved and propagated into the new container.\n\nParameters will append to base image value:\n- `volumes`\n- `exposedPorts`\n\nParameters that will append any new keys, and overwrite existing keys:\n- `labels`\n- `environment`\n\nParameters that will be overwritten:\n- `user`\n- `workingDirectory`\n- `entrypoint`\n- `cmd`\n\n## Privacy\n\nSee the [Privacy page](https://github.com/GoogleContainerTools/jib/blob/master/docs/privacy.md).\n\n## Disclaimer\n\nThis is not an officially supported Google product.\n"
  },
  {
    "path": "jib-cli/build.gradle",
    "content": "plugins {\n  id 'application'\n  id 'net.researchgate.release'\n  id 'eclipse'\n}\n\next {\n  cliMainClass = 'com.google.cloud.tools.jib.cli.JibCli'\n}\n\n// use `run` to build and run the app\n// use `installDist` or `distZip` to create an installable application\napplication {\n  applicationName = 'jib'\n  mainClass = cliMainClass\n}\n\nsourceSets.main.java.srcDirs += [\"${buildDir}/generated-src\"]\n\ntasks.register('generateSources', Copy) {\n  // to re-run the task whenever the file changes\n  inputs.file 'gradle.properties'\n\n  from fileTree('src/java-templates')\n  into \"${buildDir}/generated-src\"\n\n  rename('\\\\.template$', '')\n  expand(['version': \"${version}\"])\n}\n\ncompileJava.source generateSources\n\ndependencies {\n  implementation project(':jib-core')\n  implementation project(':jib-plugins-common')\n\n  implementation dependencyStrings.COMMONS_TEXT\n  implementation(platform(dependencyStrings.JACKSON_BOM))\n  implementation dependencyStrings.JACKSON_DATAFORMAT_YAML\n  implementation dependencyStrings.JACKSON_DATABIND\n  implementation dependencyStrings.GUAVA\n  implementation dependencyStrings.PICOCLI\n  implementation dependencyStrings.GOOGLE_HTTP_CLIENT\n  implementation dependencyStrings.GOOGLE_HTTP_CLIENT_APACHE_V2\n\n  testImplementation dependencyStrings.JUNIT\n  testImplementation dependencyStrings.JUNIT_PARAMS\n  testImplementation dependencyStrings.TRUTH\n  testImplementation dependencyStrings.TRUTH8\n  testImplementation dependencyStrings.MOCKITO_CORE\n  testImplementation dependencyStrings.SLF4J_API\n  testImplementation dependencyStrings.SYSTEM_RULES\n\n  integrationTestImplementation project(path:':jib-core', configuration:'integrationTests')\n}\n\nrelease {\n  tagTemplate = 'v$version-cli'\n  ignoredSnapshotDependencies = [\n    'com.google.cloud.tools:jib-core',\n    'com.google.cloud.tools:jib-plugins-common',\n  ]\n  git {\n    requireBranch = /^cli-release-v\\d+.*$/  //regex\n  }\n}\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\neclipse.classpath.file.whenMerged {\n  entries.each {\n    if (it.path == 'src/integration-test/resources') {\n      it.excludes += ['jarTest/', 'warTest/']\n    }\n  }\n}\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-cli/gradle.properties",
    "content": "version = 0.13.1-SNAPSHOT\n"
  },
  {
    "path": "jib-cli/scripts/update_gcs_latest.sh",
    "content": "#!/bin/bash -\n# Usage: ./jib-cli/scripts/update_gcs_latest.sh <release version>\n\nset -o errexit\n\nEchoRed() {\n\techo \"$(tput setaf 1; tput bold)$1$(tput sgr0)\"\n}\nEchoGreen() {\n\techo \"$(tput setaf 2; tput bold)$1$(tput sgr0)\"\n}\n\nDie() {\n\tEchoRed \"$1\"\n\texit 1\n}\n\n# Usage: CheckVersion <version>\nCheckVersion() {\n    [[ $1 =~ ^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z]+)?$ ]] || Die \"Version: $1 not in ###.###.###[-XXX] format.\"\n}\n\n[ $# -ne 1 ] && Die \"Usage: ./jib-cli/scripts/update_gcs_latest.sh <release version>\"\n\nCheckVersion $1\n\nversionString=\"{\\\"latest\\\":\\\"$1\\\"}\"\ndestination=\"gs://jib-versions/jib-cli\"\n\necho $versionString > jib-cli-temp\ngsutil cp jib-cli-temp $destination\ngsutil acl ch -u allUsers:READ $destination\nrm jib-cli-temp\n\ngcsResult=$(curl https://storage.googleapis.com/jib-versions/jib-cli)\nif [ \"$gcsResult\" == \"$versionString\" ]\nthen\n  EchoGreen \"Version updated successfully\"\nelse\n  Die \"Version update failed\"\nfi"
  },
  {
    "path": "jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/JarCommandTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.HttpRequestTester;\nimport com.google.common.base.Preconditions;\nimport com.google.common.io.Resources;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\nimport java.util.jar.JarOutputStream;\nimport java.util.jar.Manifest;\nimport java.util.zip.ZipEntry;\nimport javax.annotation.Nullable;\nimport javax.tools.JavaCompiler;\nimport javax.tools.JavaFileObject;\nimport javax.tools.StandardJavaFileManager;\nimport javax.tools.ToolProvider;\nimport org.junit.After;\nimport org.junit.BeforeClass;\nimport org.junit.ClassRule;\nimport org.junit.Test;\nimport picocli.CommandLine;\n\npublic class JarCommandTest {\n\n  @ClassRule\n  public static final TestProject springBootProject = new TestProject(\"jarTest/spring-boot\");\n\n  @Nullable private String containerName;\n\n  @BeforeClass\n  public static void createJars() throws IOException, URISyntaxException {\n    createJarFile(\n        \"jarWithCp.jar\", \"HelloWorld\", \"dependency1.jar directory/dependency2.jar\", \"HelloWorld\");\n    createJarFile(\"noDependencyJar.jar\", \"HelloWorld\", null, \"HelloWorld\");\n    createJarFile(\"dependency1.jar\", \"dep/A\", null, null);\n    createJarFile(\"directory/dependency2.jar\", \"dep2/B\", null, null);\n  }\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    if (containerName != null) {\n      new Command(\"docker\", \"stop\", containerName).run();\n    }\n  }\n\n  @Test\n  public void testErrorLogging_fileDoesNotExist() {\n    StringWriter stringWriter = new StringWriter();\n    CommandLine jibCli = new CommandLine(new JibCli()).setErr(new PrintWriter(stringWriter));\n\n    Integer exitCode = jibCli.execute(\"jar\", \"--target\", \"docker://jib-cli-image\", \"unknown.jar\");\n\n    assertThat(exitCode).isEqualTo(1);\n    assertThat(stringWriter.toString())\n        .isEqualTo(\"[ERROR] The file path provided does not exist: unknown.jar\\n\");\n  }\n\n  @Test\n  public void testErrorLogging_directoryGiven() {\n    StringWriter stringWriter = new StringWriter();\n    CommandLine jibCli = new CommandLine(new JibCli()).setErr(new PrintWriter(stringWriter));\n\n    Path jarFile = Paths.get(\"/\");\n    Integer exitCode =\n        jibCli.execute(\"jar\", \"--target\", \"docker://jib-cli-image\", jarFile.toString());\n\n    assertThat(exitCode).isEqualTo(1);\n    assertThat(stringWriter.toString())\n        .isEqualTo(\n            \"[ERROR] The file path provided is for a directory. Please provide a path to a JAR: \"\n                + jarFile.toString()\n                + \"\\n\");\n  }\n\n  @Test\n  public void testStandardJar_explodedMode_toDocker()\n      throws IOException, InterruptedException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(\"jarTest/standard/jarWithCp.jar\").toURI());\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://exploded-jar\",\n                jarPath.toString());\n    String output =\n        new Command(\"docker\", \"run\", \"--rm\", \"exploded-jar\", \"--privileged\", \"--network=host\")\n            .run();\n\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String classPath =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);\n\n      assertThat(classPath).isEqualTo(\"dependency1.jar directory/dependency2.jar\");\n      assertThat(exitCode).isEqualTo(0);\n      assertThat(output).isEqualTo(\"Hello World\");\n    }\n  }\n\n  @Test\n  public void testNoDependencyStandardJar_explodedMode_toDocker()\n      throws IOException, InterruptedException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(\"jarTest/standard/noDependencyJar.jar\").toURI());\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://exploded-no-dep-jar\",\n                jarPath.toString());\n    String output =\n        new Command(\n                \"docker\", \"run\", \"--rm\", \"exploded-no-dep-jar\", \"--privileged\", \"--network=host\")\n            .run();\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String classPath =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);\n\n      assertThat(classPath).isNull();\n      assertThat(exitCode).isEqualTo(0);\n      assertThat(output).isEqualTo(\"Hello World\");\n    }\n  }\n\n  @Test\n  public void testStandardJar_packagedMode_toDocker()\n      throws IOException, InterruptedException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(\"jarTest/standard/jarWithCp.jar\").toURI());\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://packaged-jar\",\n                jarPath.toString(),\n                \"--mode=packaged\");\n    String output =\n        new Command(\"docker\", \"run\", \"--rm\", \"packaged-jar\", \"--privileged\", \"--network=host\")\n            .run();\n\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String classPath =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);\n\n      assertThat(classPath).isEqualTo(\"dependency1.jar directory/dependency2.jar\");\n      assertThat(exitCode).isEqualTo(0);\n      assertThat(output).isEqualTo(\"Hello World\");\n    }\n  }\n\n  @Test\n  public void testNoDependencyStandardJar_packagedMode_toDocker()\n      throws IOException, InterruptedException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(\"jarTest/standard/noDependencyJar.jar\").toURI());\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://packaged-no-dep-jar\",\n                jarPath.toString(),\n                \"--mode=packaged\");\n    String output =\n        new Command(\n                \"docker\", \"run\", \"--rm\", \"packaged-no-dep-jar\", \"--privileged\", \"--network=host\")\n            .run();\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String classPath =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);\n\n      assertThat(classPath).isNull();\n      assertThat(exitCode).isEqualTo(0);\n      assertThat(output).isEqualTo(\"Hello World\");\n    }\n  }\n\n  @Test\n  public void testSpringBootLayeredJar_explodedMode() throws IOException, InterruptedException {\n    springBootProject.build(\"-c\", \"settings-layered.gradle\", \"clean\", \"bootJar\");\n    Path jarParentPath = springBootProject.getProjectRoot().resolve(\"build\").resolve(\"libs\");\n    Path jarPath = jarParentPath.resolve(\"spring-boot.jar\");\n\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://spring-boot-jar-layered\",\n                jarPath.toString());\n    assertThat(exitCode).isEqualTo(0);\n\n    String output =\n        new Command(\n                \"docker\",\n                \"run\",\n                \"--rm\",\n                \"--detach\",\n                \"-p8080:8080\",\n                \"spring-boot-jar-layered\",\n                \"--privileged\",\n                \"--network=host\")\n            .run();\n    containerName = output.trim();\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n\n      assertThat(jarFile.getEntry(\"BOOT-INF/layers.idx\")).isNotNull();\n      HttpRequestTester.verifyBody(\n          \"Hello world\",\n          new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080\"));\n    }\n  }\n\n  @Test\n  public void testSpringBootNonLayeredJar_explodedMode() throws IOException, InterruptedException {\n    springBootProject.build(\"clean\", \"bootJar\");\n    Path jarParentPath = springBootProject.getProjectRoot().resolve(\"build\").resolve(\"libs\");\n    Path jarPath = jarParentPath.resolve(\"spring-boot.jar\");\n\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://spring-boot-jar\",\n                jarPath.toString());\n    assertThat(exitCode).isEqualTo(0);\n\n    String output =\n        new Command(\n                \"docker\",\n                \"run\",\n                \"--rm\",\n                \"--detach\",\n                \"-p8080:8080\",\n                \"spring-boot-jar\",\n                \"--privileged\",\n                \"--network=host\")\n            .run();\n    containerName = output.trim();\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n\n      assertThat(jarFile.getEntry(\"BOOT-INF/layers.idx\")).isNull();\n      HttpRequestTester.verifyBody(\n          \"Hello world\",\n          new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080\"));\n    }\n  }\n\n  @Test\n  public void testSpringBootJar_packagedMode() throws IOException, InterruptedException {\n    springBootProject.build(\"clean\", \"bootJar\");\n    Path jarParentPath = springBootProject.getProjectRoot().resolve(\"build\").resolve(\"libs\");\n    Path jarPath = jarParentPath.resolve(\"spring-boot.jar\");\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"jar\",\n                \"--from\",\n                \"eclipse-temurin:8-jdk-focal\",\n                \"--target\",\n                \"docker://packaged-spring-boot\",\n                jarPath.toString(),\n                \"--mode=packaged\");\n    assertThat(exitCode).isEqualTo(0);\n\n    String output =\n        new Command(\n                \"docker\",\n                \"run\",\n                \"--rm\",\n                \"--detach\",\n                \"-p8080:8080\",\n                \"packaged-spring-boot\",\n                \"--privileged\",\n                \"--network=host\")\n            .run();\n    containerName = output.trim();\n\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080\"));\n  }\n\n  public static void createJarFile(\n      String name, String className, String classPath, String mainClass)\n      throws IOException, URISyntaxException {\n    Path javaFilePath =\n        Paths.get(Resources.getResource(\"jarTest/standard/\" + className + \".java\").toURI());\n    Path workingDir = Paths.get(Resources.getResource(\"jarTest/standard/\").toURI());\n\n    // compile the java file\n    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();\n    Preconditions.checkNotNull(compiler);\n    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);\n    Iterable<? extends JavaFileObject> compilationUnits =\n        fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(javaFilePath.toFile()));\n    Iterable<String> options = Arrays.asList(\"-source\", \"1.8\", \"-target\", \"1.8\");\n    JavaCompiler.CompilationTask task =\n        compiler.getTask(null, fileManager, null, options, null, compilationUnits);\n    boolean success = task.call();\n    assertThat(success).isTrue();\n\n    // Create a manifest file\n    Manifest manifest = new Manifest();\n    Attributes attributes = new Attributes();\n    attributes.putValue(\"Manifest-Version\", \"1.0\");\n    if (classPath != null) {\n      attributes.putValue(\"Class-Path\", classPath);\n    }\n    if (mainClass != null) {\n      attributes.putValue(\"Main-Class\", mainClass);\n    }\n    manifest.getMainAttributes().putAll(attributes);\n\n    // Create JAR\n    File jarFile = workingDir.resolve(name).toFile();\n    jarFile.getParentFile().mkdirs();\n    try (FileOutputStream fileOutputStream = new FileOutputStream(jarFile);\n        JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream, manifest)) {\n      ZipEntry zipEntry = new ZipEntry(className + \".class\");\n      jarOutputStream.putNextEntry(zipEntry);\n      jarOutputStream.write(Files.readAllBytes(workingDir.resolve(className + \".class\")));\n      jarOutputStream.closeEntry();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/TestProject.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.io.Resources;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.rules.TemporaryFolder;\n\npublic class TestProject extends TemporaryFolder implements Closeable {\n\n  /** Copies test project {@code projectName} to {@code destination} folder. */\n  private static void copyProject(String projectName, Path destination)\n      throws IOException, URISyntaxException {\n    Path projectPathInResources = Paths.get(Resources.getResource(projectName).toURI());\n    new DirectoryWalker(projectPathInResources)\n        .filterRoot()\n        .walk(\n            path -> {\n              // Creates the same path in the destDir.\n              Path destPath = destination.resolve(projectPathInResources.relativize(path));\n              if (Files.isDirectory(path)) {\n                Files.createDirectory(destPath);\n              } else {\n                Files.copy(path, destPath);\n              }\n            });\n  }\n\n  private final String testProjectName;\n\n  private Path projectRoot;\n\n  /** Initialize with a specific project directory. */\n  public TestProject(String testProjectName) {\n    this.testProjectName = testProjectName;\n  }\n\n  @Override\n  public void close() {\n    after();\n  }\n\n  @Override\n  protected void before() throws Throwable {\n    super.before();\n\n    projectRoot = newFolder().toPath();\n    copyProject(testProjectName, projectRoot);\n  }\n\n  public void build(String... gradleArguments) throws IOException, InterruptedException {\n    List<String> cmd = new ArrayList<>();\n    cmd.add(\"./gradlew\");\n    cmd.addAll(Arrays.asList(gradleArguments));\n    new Command(cmd).setWorkingDir(projectRoot).run();\n  }\n\n  public Path getProjectRoot() {\n    return projectRoot;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/WarCommandTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.HttpRequestTester;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.net.URL;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport javax.annotation.Nullable;\nimport org.junit.After;\nimport org.junit.ClassRule;\nimport org.junit.Test;\nimport picocli.CommandLine;\n\npublic class WarCommandTest {\n\n  @ClassRule public static final TestProject servletProject = new TestProject(\"warTest\");\n\n  @Nullable private String containerName;\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    if (containerName != null) {\n      new Command(\"docker\", \"stop\", containerName).run();\n    }\n  }\n\n  @Test\n  public void testErrorLogging_fileDoesNotExist() {\n    StringWriter stringWriter = new StringWriter();\n    CommandLine jibCli = new CommandLine(new JibCli()).setErr(new PrintWriter(stringWriter));\n\n    Integer exitCode = jibCli.execute(\"war\", \"--target\", \"docker://jib-cli-image\", \"unknown.war\");\n\n    assertThat(exitCode).isEqualTo(1);\n    assertThat(stringWriter.toString())\n        .isEqualTo(\"[ERROR] The file path provided does not exist: unknown.war\\n\");\n  }\n\n  @Test\n  public void testErrorLogging_directoryGiven() {\n    StringWriter stringWriter = new StringWriter();\n    CommandLine jibCli = new CommandLine(new JibCli()).setErr(new PrintWriter(stringWriter));\n\n    Path warFile = Paths.get(\"/\");\n    Integer exitCode =\n        jibCli.execute(\"war\", \"--target\", \"docker://jib-cli-image\", warFile.toString());\n\n    assertThat(exitCode).isEqualTo(1);\n    assertThat(stringWriter.toString())\n        .isEqualTo(\n            \"[ERROR] The file path provided is for a directory. Please provide a path to a WAR: \"\n                + warFile.toString()\n                + \"\\n\");\n  }\n\n  @Test\n  public void testWar_jetty() throws IOException, InterruptedException {\n    servletProject.build(\"clean\", \"war\");\n    Path warParentPath = servletProject.getProjectRoot().resolve(\"build\").resolve(\"libs\");\n    Path warPath = warParentPath.resolve(\"standard-war.war\");\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\"war\", \"--target\", \"docker://exploded-war\", warPath.toString());\n    assertThat(exitCode).isEqualTo(0);\n    String output =\n        new Command(\n                \"docker\",\n                \"run\",\n                \"--rm\",\n                \"--detach\",\n                \"-p8080:8080\",\n                \"exploded-war\",\n                \"--privileged\",\n                \"--network=host\")\n            .run();\n    containerName = output.trim();\n\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080/hello\"));\n  }\n\n  @Test\n  public void testWar_customJettySpecified() throws IOException, InterruptedException {\n    servletProject.build(\"clean\", \"war\");\n    Path warParentPath = servletProject.getProjectRoot().resolve(\"build\").resolve(\"libs\");\n    Path warPath = warParentPath.resolve(\"standard-war.war\");\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"war\",\n                \"--target\",\n                \"docker://exploded-war-custom-jetty\",\n                \"--from=jetty:11.0-jre11-slim-openjdk\",\n                warPath.toString());\n    assertThat(exitCode).isEqualTo(0);\n    String output =\n        new Command(\"docker\", \"run\", \"--rm\", \"--detach\", \"-p8080:8080\", \"exploded-war-custom-jetty\")\n            .run();\n    containerName = output.trim();\n\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080/hello\"));\n  }\n\n  @Test\n  public void testWar_tomcat() throws IOException, InterruptedException {\n    servletProject.build(\"clean\", \"war\");\n    Path warParentPath = servletProject.getProjectRoot().resolve(\"build\").resolve(\"libs\");\n    Path warPath = warParentPath.resolve(\"standard-war.war\");\n    Integer exitCode =\n        new CommandLine(new JibCli())\n            .execute(\n                \"war\",\n                \"--target\",\n                \"docker://exploded-war-tomcat\",\n                \"--from=tomcat:10-jre8-openjdk-slim\",\n                \"--app-root\",\n                \"/usr/local/tomcat/webapps/ROOT\",\n                warPath.toString());\n    assertThat(exitCode).isEqualTo(0);\n    String output =\n        new Command(\"docker\", \"run\", \"--rm\", \"--detach\", \"-p8080:8080\", \"exploded-war-tomcat\")\n            .run();\n    containerName = output.trim();\n\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080/hello\"));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/build-layered.gradle",
    "content": "plugins {\n    id 'org.springframework.boot' version '2.3.7.RELEASE'\n    id 'io.spring.dependency-management' version '1.0.10.RELEASE'\n    id 'java'\n}\n\nsourceCompatibility = '1.8'\n\nbootJar {\n    layered {\n        enabled = true\n    }\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n}\n\ntest {\n    useJUnitPlatform()\n}\n\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/build.gradle",
    "content": "plugins {\n    id 'org.springframework.boot' version '2.3.7.RELEASE'\n    id 'io.spring.dependency-management' version '1.0.10.RELEASE'\n    id 'java'\n}\n\nsourceCompatibility = '1.8'\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n}\n\ntest {\n    useJUnitPlatform()\n}\n\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.4-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/settings-layered.gradle",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\nrootProject.name = 'spring-boot'\nrootProject.buildFileName = 'build-layered.gradle'\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\nrootProject.name = 'spring-boot'"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/src/main/java/hello/Application.java",
    "content": "package hello;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/spring-boot/src/main/java/hello/HelloController.java",
    "content": "package hello;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HelloController {\n\n  @RequestMapping(\"/\")\n  public String index() {\n    return \"Hello world\";\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/standard/HelloWorld.java",
    "content": "public class HelloWorld {\n  public static void main(String[] args) {\n    System.out.print(\"Hello World\");\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/standard/dep/A.java",
    "content": "package dep;\n\npublic class A {\n  public static void getResult() {\n    System.out.print(\"Hello \");\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/jarTest/standard/dep2/B.java",
    "content": "package dep2;\n\npublic class B {\n  public static void getResult() {\n    System.out.print(\"World\");\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'war'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\nconfigurations {\n  moreLibs\n}\n\ndependencies {\n  providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0'\n  moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR\n}\n\nwar {\n  from ('src/extra_static')\n  from ('src/extra_js', { into 'js' })\n  classpath configurations.moreLibs\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.4-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/settings.gradle",
    "content": "rootProject.name = 'standard-war'"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/extra_js/bogus.js",
    "content": "// nothing inside\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/extra_static/bogus.html",
    "content": "nothing inside\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/main/java/example/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport jakarta.servlet.http.HttpServlet;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class HelloWorld extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {\n    try {\n      URL worldFile = getServletContext().getResource(\"/WEB-INF/classes/world\");\n      Path path = Paths.get(worldFile.toURI());\n      String world = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);\n\n      response.setContentType(\"text/plain\");\n      response.setCharacterEncoding(\"UTF-8\");\n\n      response.getWriter().print(\"Hello \" + world);\n\n    } catch (URISyntaxException e) {\n      throw new IOException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/main/webapp/META-INF/MANIFEST.MF",
    "content": "Manifest-Version: 1.0\nClass-Path: \n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Using the old Servlet API 2.5 for demonstration purposes. -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         version=\"2.5\">\n  <servlet>\n    <servlet-name>HelloWorld</servlet-name>\n    <servlet-class>example.HelloWorld</servlet-class>\n  </servlet>\n  <servlet-mapping>\n    <servlet-name>HelloWorld</servlet-name>\n    <url-pattern>/hello</url-pattern>\n  </servlet-mapping>\n</web-app>\n"
  },
  {
    "path": "jib-cli/src/integration-test/resources/warTest/src/main/webapp/index.html",
    "content": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=UTF-8\" />\n    <title>Hello World</title>\n  </head>\n\n  <body>\n    <h1>Hello World!</h1>\n\n    <table>\n      <tr>\n        <td colspan=\"2\" style=\"font-weight:bold;\">Available Servlets:</td>\n      </tr>\n      <tr>\n        <td><a href='/hello'>The HelloWorld servlet</a></td>\n      </tr>\n    </table>\n  </body>\n</html>\n"
  },
  {
    "path": "jib-cli/src/java-templates/com/google/cloud/tools/jib/cli/VersionInfo.java.template",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport picocli.CommandLine;\n\npublic class VersionInfo implements CommandLine.IVersionProvider {\n  public static final String TOOL_NAME = \"jib-cli\";\n\n  @Override\n  public String[] getVersion() throws Exception {\n    return new String[] {getVersionSimple()};\n  }\n\n  public static String getVersionSimple() {\n    return \"${version}\";\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactLayers.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.function.Predicate;\n\npublic class ArtifactLayers {\n\n  // Used for layer names.\n  public static final String CLASSES = \"classes\";\n  public static final String RESOURCES = \"resources\";\n  public static final String DEPENDENCIES = \"dependencies\";\n  public static final String SNAPSHOT_DEPENDENCIES = \"snapshot dependencies\";\n\n  private ArtifactLayers() {}\n\n  /**\n   * Creates a layer containing contents of a directory. Only paths that match the given predicate\n   * will be added.\n   *\n   * @param layerName name of the layer\n   * @param sourceRoot path to source directory\n   * @param pathFilter predicate to determine whether to add the path or not\n   * @param basePathInContainer path to destination on container\n   * @return {@link FileEntriesLayer} representing the layer\n   * @throws IOException if io exception occurs when reading from the source directory\n   */\n  public static FileEntriesLayer getDirectoryContentsAsLayer(\n      String layerName,\n      Path sourceRoot,\n      Predicate<Path> pathFilter,\n      AbsoluteUnixPath basePathInContainer)\n      throws IOException {\n    FileEntriesLayer.Builder builder = FileEntriesLayer.builder().setName(layerName);\n    new DirectoryWalker(sourceRoot)\n        .filterRoot()\n        .filter(path -> pathFilter.test(path))\n        .walk(\n            path -> {\n              AbsoluteUnixPath pathOnContainer =\n                  basePathInContainer.resolve(sourceRoot.relativize(path));\n              builder.addEntry(path, pathOnContainer);\n            });\n    return builder.build();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactProcessor.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.List;\n\n/** Interface to create layers and compute entrypoint from JAR or WAR file contents. */\npublic interface ArtifactProcessor {\n\n  /**\n   * Creates layers on container for a JAR or WAR.\n   *\n   * @return list of {@link FileEntriesLayer}\n   * @throws IOException if I/O error occurs when opening the java artifact or if temporary\n   *     directory provided doesn't exist\n   */\n  List<FileEntriesLayer> createLayers() throws IOException;\n\n  /**\n   * Computes the entrypoint for a JAR or WAR.\n   *\n   * @param jvmFlags list of jvm flags\n   * @return list of {@link String} representing entrypoint\n   * @throws IOException if I/O error occurs when opening the java artifact\n   */\n  ImmutableList<String> computeEntrypoint(List<String> jvmFlags) throws IOException;\n\n  Integer getJavaVersion();\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactProcessors.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.cli.jar.ProcessingMode;\nimport com.google.cloud.tools.jib.cli.jar.SpringBootExplodedProcessor;\nimport com.google.cloud.tools.jib.cli.jar.SpringBootPackagedProcessor;\nimport com.google.cloud.tools.jib.cli.jar.StandardExplodedProcessor;\nimport com.google.cloud.tools.jib.cli.jar.StandardPackagedProcessor;\nimport com.google.cloud.tools.jib.cli.war.StandardWarExplodedProcessor;\nimport java.io.DataInputStream;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.nio.file.Path;\nimport java.util.Enumeration;\nimport java.util.Optional;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\n\n/**\n * Class to create a {@link ArtifactProcessor} instance depending on jar or war type and processing\n * mode.\n */\npublic class ArtifactProcessors {\n  private static String SPRING_BOOT = \"spring-boot\";\n  private static String STANDARD = \"standard\";\n  private static Integer VERSION_NOT_FOUND = 0;\n  private static final String DEFAULT_JETTY_APP_ROOT = \"/var/lib/jetty/webapps/ROOT\";\n\n  private ArtifactProcessors() {}\n\n  /**\n   * Creates a {@link ArtifactProcessor} instance based on jar type and processing mode.\n   *\n   * @param jarPath path to the jar\n   * @param cacheDirectories the location of the relevant caches\n   * @param jarOptions jar cli options\n   * @param commonContainerConfigCliOptions common cli options shared between jar and war command\n   * @return ArtifactProcessor\n   * @throws IOException if I/O error occurs when opening the jar file\n   */\n  public static ArtifactProcessor fromJar(\n      Path jarPath,\n      CacheDirectories cacheDirectories,\n      Jar jarOptions,\n      CommonContainerConfigCliOptions commonContainerConfigCliOptions)\n      throws IOException {\n    Integer jarJavaVersion = determineJavaMajorVersion(jarPath);\n    if (jarJavaVersion > 17 && !commonContainerConfigCliOptions.getFrom().isPresent()) {\n      throw new IllegalStateException(\n          String.format(\n              \"The input JAR (%s) is compiled with Java %d, but the default base image only \"\n                  + \"supports versions up to Java 17. Specify a custom base image with --from.\",\n              jarPath, jarJavaVersion));\n    }\n    String jarType = determineJarType(jarPath);\n    ProcessingMode mode = jarOptions.getMode();\n    if (jarType.equals(SPRING_BOOT) && mode.equals(ProcessingMode.packaged)) {\n      return new SpringBootPackagedProcessor(jarPath, jarJavaVersion);\n    } else if (jarType.equals(SPRING_BOOT) && mode.equals(ProcessingMode.exploded)) {\n      return new SpringBootExplodedProcessor(\n          jarPath, cacheDirectories.getExplodedArtifactDirectory(), jarJavaVersion);\n    } else if (jarType.equals(STANDARD) && mode.equals(ProcessingMode.packaged)) {\n      return new StandardPackagedProcessor(jarPath, jarJavaVersion);\n    } else {\n      return new StandardExplodedProcessor(\n          jarPath, cacheDirectories.getExplodedArtifactDirectory(), jarJavaVersion);\n    }\n  }\n\n  /**\n   * Creates a {@link ArtifactProcessor} instance.\n   *\n   * @param warPath path to the war\n   * @param cacheDirectories the location of the relevant caches\n   * @param warOptions war cli options\n   * @param commonContainerConfigCliOptions common cli options shared between jar and war command\n   * @return ArtifactProcessor\n   * @throws InvalidImageReferenceException if base image reference is invalid\n   */\n  public static ArtifactProcessor fromWar(\n      Path warPath,\n      CacheDirectories cacheDirectories,\n      War warOptions,\n      CommonContainerConfigCliOptions commonContainerConfigCliOptions)\n      throws InvalidImageReferenceException {\n    Optional<AbsoluteUnixPath> appRoot = warOptions.getAppRoot();\n    if (!commonContainerConfigCliOptions.isJettyBaseimage() && !appRoot.isPresent()) {\n      throw new IllegalArgumentException(\n          \"Please set the app root of the container with `--app-root` when specifying a base image that is not jetty.\");\n    }\n    AbsoluteUnixPath chosenAppRoot = appRoot.orElse(AbsoluteUnixPath.get(DEFAULT_JETTY_APP_ROOT));\n    return new StandardWarExplodedProcessor(\n        warPath, cacheDirectories.getExplodedArtifactDirectory(), chosenAppRoot);\n  }\n\n  /**\n   * Determines whether the jar is a spring boot or standard jar.\n   *\n   * @param jarPath path to the jar\n   * @return the jar type\n   * @throws IOException if I/O error occurs when opening the file\n   */\n  private static String determineJarType(Path jarPath) throws IOException {\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      if (jarFile.getEntry(\"BOOT-INF\") != null) {\n        return SPRING_BOOT;\n      }\n      return STANDARD;\n    }\n  }\n\n  /**\n   * Determines the java version of JAR. Derives the version from the first .class file it finds in\n   * the JAR.\n   *\n   * @param jarPath path to the jar\n   * @return java version\n   * @throws IOException if I/O exception thrown when opening the jar file\n   */\n  public static Integer determineJavaMajorVersion(Path jarPath) throws IOException {\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      Enumeration<JarEntry> jarEntries = jarFile.entries();\n      while (jarEntries.hasMoreElements()) {\n        String jarEntry = jarEntries.nextElement().toString();\n        if (jarEntry.endsWith(\".class\") && !jarEntry.endsWith(\"module-info.class\")) {\n          try (URLClassLoader loader = new URLClassLoader(new URL[] {jarPath.toUri().toURL()});\n              DataInputStream classFile =\n                  new DataInputStream(loader.getResourceAsStream(jarEntry))) {\n\n            // Check magic number\n            if (classFile.readInt() != 0xCAFEBABE) {\n              throw new IllegalArgumentException(\n                  \"The class file (\" + jarEntry + \") is of an invalid format.\");\n            }\n\n            // Skip over minor version\n            classFile.skipBytes(2);\n\n            int majorVersion = classFile.readUnsignedShort();\n            int javaVersion = (majorVersion - 45) + 1;\n            return javaVersion;\n          } catch (EOFException ex) {\n            throw new IllegalArgumentException(\n                \"Reached end of class file (\"\n                    + jarEntry\n                    + \") before being able to read the java major version. Make sure that the file is of the correct format.\");\n          }\n        }\n      }\n      return VERSION_NOT_FOUND;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/Build.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.cli.buildfile.BuildFiles;\nimport com.google.cloud.tools.jib.cli.logging.CliLogger;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.Multimaps;\nimport com.google.common.util.concurrent.Futures;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Future;\nimport picocli.CommandLine;\nimport picocli.CommandLine.Model.CommandSpec;\n\n@CommandLine.Command(\n    name = \"build\",\n    mixinStandardHelpOptions = true,\n    showAtFileInUsageHelp = true,\n    description = \"Build a container\")\npublic class Build implements Callable<Integer> {\n\n  @CommandLine.Spec\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private CommandSpec spec;\n\n  @CommandLine.Mixin\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  @VisibleForTesting\n  CommonCliOptions commonCliOptions;\n\n  @VisibleForTesting\n  @CommandLine.Option(\n      names = {\"-c\", \"--context\"},\n      defaultValue = \".\",\n      paramLabel = \"<project-root>\",\n      description = \"The context root directory of the build (ex: path/to/my/build/things)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  Path contextRoot;\n\n  @VisibleForTesting\n  @CommandLine.Option(\n      names = {\"-b\", \"--build-file\"},\n      paramLabel = \"<build-file>\",\n      description = \"The path to the build file (ex: path/to/other-jib.yaml)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  Path buildFileUnprocessed;\n\n  @CommandLine.Option(\n      names = {\"-p\", \"--parameter\"},\n      paramLabel = \"<name>=<value>\",\n      description =\n          \"templating parameter to inject into build file, replace $${<name>} with <value> (repeatable)\")\n  private Map<String, String> templateParameters = Collections.emptyMap();\n\n  /**\n   * Returns a user configured Path to a buildfile and if none is configured returns jib.yaml in\n   * {@link #contextRoot}.\n   *\n   * @return a path to a buildfile\n   */\n  @VisibleForTesting\n  Path getBuildFile() {\n    if (buildFileUnprocessed == null) {\n      return contextRoot.resolve(\"jib.yaml\");\n    }\n    return buildFileUnprocessed;\n  }\n\n  public Map<String, String> getTemplateParameters() {\n    return templateParameters;\n  }\n\n  @Override\n  public Integer call() {\n    commonCliOptions.validate();\n    Path buildFile = getBuildFile();\n    SingleThreadedExecutor executor = new SingleThreadedExecutor();\n    ConsoleLogger logger =\n        CliLogger.newLogger(\n            commonCliOptions.getVerbosity(),\n            commonCliOptions.getHttpTrace(),\n            commonCliOptions.getConsoleOutput(),\n            spec.commandLine().getOut(),\n            spec.commandLine().getErr(),\n            executor);\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());\n    try {\n      JibCli.configureHttpLogging(commonCliOptions.getHttpTrace().toJulLevel());\n      GlobalConfig globalConfig = GlobalConfig.readConfig();\n      updateCheckFuture =\n          JibCli.newUpdateChecker(\n              globalConfig,\n              commonCliOptions.getVerbosity(),\n              logEvent -> logger.log(logEvent.getLevel(), logEvent.getMessage()));\n      if (!Files.isReadable(buildFile)) {\n        logger.log(\n            LogEvent.Level.ERROR,\n            \"The Build File YAML either does not exist or cannot be opened for reading: \"\n                + buildFile);\n        return 1;\n      }\n      if (!Files.isRegularFile(buildFile)) {\n        logger.log(LogEvent.Level.ERROR, \"Build File YAML path is a not a file: \" + buildFile);\n        return 1;\n      }\n\n      CacheDirectories cacheDirectories = CacheDirectories.from(commonCliOptions, contextRoot);\n      Containerizer containerizer = Containerizers.from(commonCliOptions, logger, cacheDirectories);\n\n      JibContainerBuilder containerBuilder =\n          BuildFiles.toJibContainerBuilder(contextRoot, buildFile, this, commonCliOptions, logger);\n\n      // Enable registry mirrors\n      Multimaps.asMap(globalConfig.getRegistryMirrors()).forEach(containerizer::addRegistryMirrors);\n\n      JibContainer jibContainer = containerBuilder.containerize(containerizer);\n      JibCli.writeImageJson(commonCliOptions.getImageJsonPath(), jibContainer);\n    } catch (InterruptedException ex) {\n      JibCli.logTerminatingException(logger, ex, commonCliOptions.isStacktrace());\n      Thread.currentThread().interrupt();\n      return 1;\n    } catch (Exception ex) {\n      JibCli.logTerminatingException(logger, ex, commonCliOptions.isStacktrace());\n      return 1;\n    } finally {\n      JibCli.finishUpdateChecker(logger, updateCheckFuture);\n      executor.shutDownAndAwaitTermination(Duration.ofSeconds(3));\n    }\n    return 0;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/CacheDirectories.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Charsets;\nimport com.google.common.base.Preconditions;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/** A class to determine cache locations for any cli commands. */\npublic class CacheDirectories {\n\n  private static final String APPLICATION_LAYER_CACHE_DIR = \"application-layers\";\n  private static final String EXPLODED_ARTIFACT_DIR = \"exploded-artifact\";\n\n  @Nullable private final Path baseImageCache;\n  private final Path projectCache;\n\n  /**\n   * Create a caches helper for cli cache locations.\n   *\n   * @param commonCliOptions cli options for user configured cache directories\n   * @param contextRoot the context root, use the parent directory of single files, this context\n   *     root must exist\n   * @return an instance of CacheDirectories with cli specific cache locations\n   */\n  public static CacheDirectories from(CommonCliOptions commonCliOptions, Path contextRoot) {\n    Preconditions.checkArgument(\n        Files.isDirectory(contextRoot),\n        \"contextRoot must be a directory, but \" + contextRoot.toString() + \" is not.\");\n    return new CacheDirectories(\n        commonCliOptions.getBaseImageCache().orElse(null),\n        commonCliOptions\n            .getProjectCache()\n            .orElse(\n                Paths.get(System.getProperty(\"java.io.tmpdir\"))\n                    .resolve(\"jib-cli-cache\")\n                    .resolve(\"projects\")\n                    .resolve(getProjectCacheDirectoryFromProject(contextRoot))));\n  }\n\n  @VisibleForTesting\n  static String getProjectCacheDirectoryFromProject(Path path) {\n    try {\n      byte[] hashedBytes =\n          MessageDigest.getInstance(\"SHA-256\")\n              .digest(path.toFile().getCanonicalPath().getBytes(Charsets.UTF_8));\n      StringBuilder stringBuilder = new StringBuilder(2 * hashedBytes.length);\n      for (byte b : hashedBytes) {\n        stringBuilder.append(String.format(\"%02x\", b));\n      }\n      return stringBuilder.toString();\n    } catch (IOException | SecurityException ex) {\n      throw new RuntimeException(\n          \"Unable to create cache directory for project path: \"\n              + path\n              + \" - you can try to configure --project-cache manually\",\n          ex);\n    } catch (NoSuchAlgorithmException ex) {\n      throw new RuntimeException(\n          \"SHA-256 algorithm implementation not found - might be a broken JVM\");\n    }\n  }\n\n  public CacheDirectories(@Nullable Path baseImageCache, Path projectCache) {\n    this.baseImageCache = baseImageCache;\n    this.projectCache = projectCache;\n  }\n\n  public Optional<Path> getBaseImageCache() {\n    return Optional.ofNullable(baseImageCache);\n  }\n\n  public Path getProjectCache() {\n    return projectCache;\n  }\n\n  public Path getApplicationLayersCache() {\n    return projectCache.resolve(APPLICATION_LAYER_CACHE_DIR);\n  }\n\n  public Path getExplodedArtifactDirectory() {\n    return projectCache.resolve(EXPLODED_ARTIFACT_DIR);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/CommonCliOptions.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.cloud.tools.jib.api.Jib.TAR_IMAGE_PREFIX;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.cli.logging.ConsoleOutput;\nimport com.google.cloud.tools.jib.cli.logging.HttpTraceLevel;\nimport com.google.cloud.tools.jib.cli.logging.Verbosity;\nimport com.google.common.base.Verify;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport picocli.CommandLine;\nimport picocli.CommandLine.Model.CommandSpec;\n\npublic class CommonCliOptions {\n\n  @CommandLine.Spec\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private CommandSpec spec;\n\n  @CommandLine.Option(\n      names = {\"-t\", \"--target\"},\n      required = true,\n      paramLabel = \"<target-image>\",\n      description =\n          \"The destination image reference or jib style url,%nexamples:%n gcr.io/project/image,%n registry://image-ref,%n docker://image,%n tar://path\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  static String targetImage;\n\n  // unfortunately we cannot verify for --target=tar://... this is required, we must do this after\n  // pico cli is done parsing\n  @CommandLine.Option(\n      names = \"--name\",\n      paramLabel = \"<image-reference>\",\n      description =\n          \"The image reference to inject into the tar configuration (required when using --target tar://...)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private String name;\n\n  @CommandLine.Option(\n      names = \"--additional-tags\",\n      paramLabel = \"<tag>\",\n      split = \",\",\n      description = \"Additional tags for target image\")\n  private List<String> additionalTags = Collections.emptyList();\n\n  @CommandLine.Option(\n      names = \"--base-image-cache\",\n      paramLabel = \"<cache-directory>\",\n      description = \"A path to a base image cache\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Path baseImageCache;\n\n  @CommandLine.Option(\n      names = \"--project-cache\",\n      paramLabel = \"<cache-directory>\",\n      description = \"A path to the project cache\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Path projectCache;\n\n  // Auth/Security\n  @CommandLine.Option(\n      names = \"--allow-insecure-registries\",\n      description = \"Allow jib to communicate with registries over http (insecure)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private boolean allowInsecureRegistries;\n\n  @CommandLine.Option(\n      names = \"--send-credentials-over-http\",\n      description = \"Allow jib to send credentials over http (very insecure)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private boolean sendCredentialsOverHttp;\n\n  @CommandLine.ArgGroup(exclusive = true)\n  @SuppressWarnings(\"NullAway.Init\")\n  private Credentials credentials;\n\n  private static class Credentials {\n    @CommandLine.Option(\n        names = {\"--credential-helper\"},\n        paramLabel = \"<credential-helper>\",\n        description =\n            \"credential helper for communicating with both target and base image registries, either a path to the helper, or a suffix for an executable named `docker-credential-<suffix>`\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private String credentialHelper;\n\n    @CommandLine.ArgGroup(exclusive = false)\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private SingleUsernamePassword usernamePassword;\n\n    @CommandLine.ArgGroup(exclusive = false)\n    @SuppressWarnings(\"NullAway.Init\")\n    private SeparateCredentials separate;\n  }\n\n  private static class SingleUsernamePassword {\n    @CommandLine.Option(\n        names = \"--username\",\n        required = true,\n        description = \"username for communicating with both target and base image registries\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    String username;\n\n    @CommandLine.Option(\n        names = \"--password\",\n        arity = \"0..1\",\n        required = true,\n        interactive = true,\n        description = \"password for communicating with both target and base image registries\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    String password;\n  }\n\n  private static class SeparateCredentials {\n    @CommandLine.ArgGroup\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private ToCredentials to;\n\n    @CommandLine.ArgGroup\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private FromCredentials from;\n  }\n\n  private static class ToCredentials {\n    @CommandLine.Option(\n        names = {\"--to-credential-helper\"},\n        paramLabel = \"<credential-helper>\",\n        description =\n            \"credential helper for communicating with target registry, either a path to the helper, or a suffix for an executable named `docker-credential-<suffix>`\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private String credentialHelper;\n\n    @CommandLine.ArgGroup(exclusive = false)\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private ToUsernamePassword usernamePassword;\n  }\n\n  private static class FromCredentials {\n    @CommandLine.Option(\n        names = {\"--from-credential-helper\"},\n        paramLabel = \"<credential-helper>\",\n        description =\n            \"credential helper for communicating with base image registry, either a path to the helper, or a suffix for an executable named `docker-credential-<suffix>`\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private String credentialHelper;\n\n    @CommandLine.ArgGroup(exclusive = false)\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    private FromUsernamePassword usernamePassword;\n  }\n\n  private static class ToUsernamePassword {\n    @CommandLine.Option(\n        names = \"--to-username\",\n        required = true,\n        description = \"username for communicating with target image registry\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    String username;\n\n    @CommandLine.Option(\n        names = \"--to-password\",\n        arity = \"0..1\",\n        interactive = true,\n        required = true,\n        description = \"password for communicating with target image registry\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    String password;\n  }\n\n  private static class FromUsernamePassword {\n    @CommandLine.Option(\n        names = \"--from-username\",\n        required = true,\n        description = \"username for communicating with base image registry\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    String username;\n\n    @CommandLine.Option(\n        names = \"--from-password\",\n        arity = \"0..1\",\n        required = true,\n        interactive = true,\n        description = \"password for communicating with base image registry\")\n    @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n    String password;\n  }\n\n  @CommandLine.Option(\n      names = \"--verbosity\",\n      paramLabel = \"<level>\",\n      defaultValue = \"lifecycle\",\n      description =\n          \"set logging verbosity, candidates: ${COMPLETION-CANDIDATES}, default: ${DEFAULT-VALUE}\",\n      scope = CommandLine.ScopeType.INHERIT)\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Verbosity verbosity;\n\n  @CommandLine.Option(\n      names = \"--console\",\n      paramLabel = \"<type>\",\n      defaultValue = \"auto\",\n      description =\n          \"set console output type, candidates: ${COMPLETION-CANDIDATES}, default: ${DEFAULT-VALUE}\",\n      scope = CommandLine.ScopeType.INHERIT)\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private ConsoleOutput consoleOutput;\n\n  // Hidden debug parameters\n  @CommandLine.Option(names = \"--stacktrace\", hidden = true, scope = CommandLine.ScopeType.INHERIT)\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private boolean stacktrace;\n\n  @CommandLine.Option(\n      names = \"--http-trace\",\n      hidden = true,\n      arity = \"0..1\",\n      defaultValue = \"off\",\n      fallbackValue = \"config\",\n      description =\n          \"set http logging level, candidates: ${COMPLETION-CANDIDATES}, default: ${DEFAULT-VALUE}\",\n      scope = CommandLine.ScopeType.INHERIT)\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private HttpTraceLevel httpTrace;\n\n  @CommandLine.Option(names = \"--serialize\", hidden = true, scope = CommandLine.ScopeType.INHERIT)\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private boolean serialize;\n\n  @CommandLine.Option(\n      names = \"--image-metadata-out\",\n      paramLabel = \"<path-to-json>\",\n      description =\n          \"path to the json file that should contain image metadata (for example, digest, id and tags) after build is\"\n              + \"complete\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Path imageJsonPath;\n\n  public Verbosity getVerbosity() {\n    return Verify.verifyNotNull(verbosity);\n  }\n\n  public ConsoleOutput getConsoleOutput() {\n    return Verify.verifyNotNull(consoleOutput);\n  }\n\n  public boolean isStacktrace() {\n    return stacktrace;\n  }\n\n  public HttpTraceLevel getHttpTrace() {\n    return httpTrace;\n  }\n\n  public boolean isSerialize() {\n    return serialize;\n  }\n\n  public String getTargetImage() {\n    return targetImage;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  /**\n   * Returns the user configured credential helper for any registry during the build, this can be\n   * interpreted as a path or a string.\n   *\n   * @return a Optional string reference to the credential helper\n   */\n  public Optional<String> getCredentialHelper() {\n    if (credentials != null && credentials.credentialHelper != null) {\n      return Optional.of(credentials.credentialHelper);\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * Returns the user configured credential helper for the target image registry, this can be\n   * interpreted as a path or a string.\n   *\n   * @return a Optional string reference to the credential helper\n   */\n  public Optional<String> getToCredentialHelper() {\n    if (credentials != null\n        && credentials.separate != null\n        && credentials.separate.to != null\n        && credentials.separate.to.credentialHelper != null) {\n      return Optional.of(credentials.separate.to.credentialHelper);\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * Returns the user configured credential helper for the base image registry, this can be\n   * interpreted as a path or a string.\n   *\n   * @return a Optional string reference to the credential helper\n   */\n  public Optional<String> getFromCredentialHelper() {\n    if (credentials != null\n        && credentials.separate != null\n        && credentials.separate.from != null\n        && credentials.separate.from.credentialHelper != null) {\n      return Optional.of(credentials.separate.from.credentialHelper);\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * If configured, returns a {@link Credential} created from user configured username/password.\n   *\n   * @return an optional Credential\n   */\n  public Optional<Credential> getUsernamePassword() {\n    if (credentials != null && credentials.usernamePassword != null) {\n      return Optional.of(\n          Credential.from(\n              Verify.verifyNotNull(credentials.usernamePassword.username),\n              Verify.verifyNotNull(credentials.usernamePassword.password)));\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * If configured, returns a {@link Credential} created from user configured \"to\"\n   * username/password.\n   *\n   * @return a optional Credential\n   */\n  public Optional<Credential> getToUsernamePassword() {\n    if (credentials != null\n        && credentials.separate != null\n        && credentials.separate.to != null\n        && credentials.separate.to.usernamePassword != null) {\n      return Optional.of(\n          Credential.from(\n              Verify.verifyNotNull(credentials.separate.to.usernamePassword.username),\n              Verify.verifyNotNull(credentials.separate.to.usernamePassword.password)));\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * If configured, returns a {@link Credential} created from user configured \"from\"\n   * username/password.\n   *\n   * @return a optional Credential\n   */\n  public Optional<Credential> getFromUsernamePassword() {\n    if (credentials != null\n        && credentials.separate != null\n        && credentials.separate.from != null\n        && credentials.separate.from.usernamePassword != null) {\n      return Optional.of(\n          Credential.from(\n              Verify.verifyNotNull(credentials.separate.from.usernamePassword.username),\n              Verify.verifyNotNull(credentials.separate.from.usernamePassword.password)));\n    }\n    return Optional.empty();\n  }\n\n  public boolean isAllowInsecureRegistries() {\n    return allowInsecureRegistries;\n  }\n\n  public boolean isSendCredentialsOverHttp() {\n    return sendCredentialsOverHttp;\n  }\n\n  /**\n   * Do not use directly, use {@link CacheDirectories} instead.\n   *\n   * @return a user configured base image cache\n   */\n  public Optional<Path> getBaseImageCache() {\n    return Optional.ofNullable(baseImageCache);\n  }\n\n  /**\n   * Do not use directly, use {@link CacheDirectories} instead.\n   *\n   * @return a user configured project cache\n   */\n  public Optional<Path> getProjectCache() {\n    return Optional.ofNullable(projectCache);\n  }\n\n  public List<String> getAdditionalTags() {\n    return additionalTags;\n  }\n\n  /**\n   * Returns the full path to the image metadata json file, if provided.\n   *\n   * @return optional value of path to image json file\n   */\n  public Optional<Path> getImageJsonPath() {\n    return Optional.ofNullable(imageJsonPath);\n  }\n\n  /** Validates parameters defined in this class that could not be done declaratively. */\n  public void validate() {\n    if (targetImage.startsWith(TAR_IMAGE_PREFIX) && name == null) {\n      throw new CommandLine.ParameterException(\n          spec.commandLine(),\n          \"Missing option: --name must be specified when using --target=tar://....\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/CommonContainerConfigCliOptions.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Ports;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.common.collect.ImmutableSet;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport picocli.CommandLine;\n\n/** Common command line options shared between jar and war command. */\npublic class CommonContainerConfigCliOptions {\n\n  @CommandLine.Option(\n      names = \"--from\",\n      paramLabel = \"<base-image>\",\n      description = \"The base image to use.\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private String from;\n\n  @CommandLine.Option(\n      names = \"--expose\",\n      paramLabel = \"<port>\",\n      split = \",\",\n      description = \"Ports to expose on container, example: --expose=5000,7/udp.\")\n  private List<String> exposedPorts = Collections.emptyList();\n\n  @CommandLine.Option(\n      names = \"--volumes\",\n      paramLabel = \"<volume>\",\n      split = \",\",\n      description =\n          \"Directories on container to hold extra volumes,  example: --volumes=/var/log,/var/log2.\")\n  private List<String> volumes = Collections.emptyList();\n\n  @CommandLine.Option(\n      names = \"--environment-variables\",\n      paramLabel = \"<key>=<value>\",\n      split = \",\",\n      description =\n          \"Environment variables to write into container, example: --environment-variables env1=env_value1,env2=env_value2.\")\n  private Map<String, String> environment = Collections.emptyMap();\n\n  @CommandLine.Option(\n      names = \"--labels\",\n      paramLabel = \"<key>=<value>\",\n      split = \",\",\n      description =\n          \"Labels to write into container metadata, example: --labels=label1=value1,label2=value2.\")\n  private Map<String, String> labels = Collections.emptyMap();\n\n  @CommandLine.Option(\n      names = {\"-u\", \"--user\"},\n      paramLabel = \"<user>\",\n      description = \"The user to run the container as, example: --user=myuser:mygroup.\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private String user;\n\n  @CommandLine.Option(\n      names = {\"--image-format\"},\n      defaultValue = \"Docker\",\n      paramLabel = \"<image-format>\",\n      description =\n          \"Format of container, candidates: ${COMPLETION-CANDIDATES}, default: ${DEFAULT-VALUE}.\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private ImageFormat format;\n\n  @CommandLine.Option(\n      names = \"--program-args\",\n      paramLabel = \"<program-argument>\",\n      split = \",\",\n      description = \"Program arguments for container entrypoint.\")\n  private List<String> programArguments = Collections.emptyList();\n\n  @CommandLine.Option(\n      names = \"--entrypoint\",\n      paramLabel = \"<entrypoint>\",\n      split = \"\\\\s+\",\n      description =\n          \"Entrypoint for container. Overrides the default entrypoint, example: --entrypoint='custom entrypoint'\")\n  private List<String> entrypoint = Collections.emptyList();\n\n  @CommandLine.Option(\n      names = \"--creation-time\",\n      paramLabel = \"<creation-time>\",\n      description =\n          \"The creation time of the container in milliseconds since epoch or iso8601 format. Overrides the default (1970-01-01T00:00:00Z)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private String creationTime;\n\n  /**\n   * Returns the user-specified base image.\n   *\n   * @return an optional base image\n   */\n  public Optional<String> getFrom() {\n    return Optional.ofNullable(from);\n  }\n\n  /**\n   * Returns set of {@link Port} representing ports to be exposed on container (if specified).\n   *\n   * @return set of exposed ports\n   */\n  public Set<Port> getExposedPorts() {\n    return (exposedPorts == null) ? ImmutableSet.of() : Ports.parse(exposedPorts);\n  }\n\n  /**\n   * Returns a set of {@link AbsoluteUnixPath} representing directories on container to hold volumes\n   * (if specified).\n   *\n   * @return set of volumes\n   */\n  public Set<AbsoluteUnixPath> getVolumes() {\n    if (volumes == null) {\n      return ImmutableSet.of();\n    }\n    return volumes.stream().map(AbsoluteUnixPath::get).collect(Collectors.toSet());\n  }\n\n  public Map<String, String> getEnvironment() {\n    return environment;\n  }\n\n  public Map<String, String> getLabels() {\n    return labels;\n  }\n\n  public Optional<String> getUser() {\n    return Optional.ofNullable(user);\n  }\n\n  public Optional<ImageFormat> getFormat() {\n    return Optional.ofNullable(format);\n  }\n\n  public List<String> getProgramArguments() {\n    return programArguments;\n  }\n\n  public List<String> getEntrypoint() {\n    return entrypoint;\n  }\n\n  /**\n   * Returns {@link Instant} representing creation time of container.\n   *\n   * @return an optional creation time\n   */\n  public Optional<Instant> getCreationTime() {\n    if (creationTime != null) {\n      return Optional.of(Instants.fromMillisOrIso8601(creationTime, \"creationTime\"));\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * Returns {@code true} if the user-specified base image is jetty or if a custom base image is not\n   * specified.\n   *\n   * @return a boolean\n   * @throws InvalidImageReferenceException if image reference is invalid\n   */\n  public Boolean isJettyBaseimage() throws InvalidImageReferenceException {\n    if (from != null) {\n      ImageReference baseImageReference = ImageReference.parse(from);\n      return baseImageReference.getRegistry().equals(\"registry-1.docker.io\")\n          && baseImageReference.getRepository().equals(\"library/jetty\");\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ContainerBuilders.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.cloud.tools.jib.api.Jib.DOCKER_DAEMON_IMAGE_PREFIX;\nimport static com.google.cloud.tools.jib.api.Jib.REGISTRY_IMAGE_PREFIX;\nimport static com.google.cloud.tools.jib.api.Jib.TAR_IMAGE_PREFIX;\n\nimport com.google.cloud.tools.jib.api.DockerDaemonImage;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.TarImage;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory;\nimport com.google.cloud.tools.jib.plugins.common.DefaultCredentialRetrievers;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport java.io.FileNotFoundException;\nimport java.nio.file.Paths;\nimport java.util.Set;\n\n/** Helper class for creating JibContainerBuilders from JibCli specifications. */\npublic class ContainerBuilders {\n\n  private ContainerBuilders() {}\n\n  /**\n   * Creates a {@link JibContainerBuilder} depending on the base image specified.\n   *\n   * @param baseImageReference base image reference\n   * @param platforms platforms for multi-platform support in build command\n   * @param commonCliOptions common cli options\n   * @param logger console logger\n   * @return a {@link JibContainerBuilder}\n   * @throws InvalidImageReferenceException if the baseImage reference cannot be parsed\n   * @throws FileNotFoundException if credential helper file cannot be found\n   */\n  public static JibContainerBuilder create(\n      String baseImageReference,\n      Set<Platform> platforms,\n      CommonCliOptions commonCliOptions,\n      ConsoleLogger logger)\n      throws InvalidImageReferenceException, FileNotFoundException {\n    if (baseImageReference.startsWith(DOCKER_DAEMON_IMAGE_PREFIX)) {\n      return Jib.from(\n          DockerDaemonImage.named(baseImageReference.replaceFirst(DOCKER_DAEMON_IMAGE_PREFIX, \"\")));\n    }\n    if (baseImageReference.startsWith(TAR_IMAGE_PREFIX)) {\n      return Jib.from(\n          TarImage.at(Paths.get(baseImageReference.replaceFirst(TAR_IMAGE_PREFIX, \"\"))));\n    }\n    ImageReference imageReference =\n        ImageReference.parse(baseImageReference.replaceFirst(REGISTRY_IMAGE_PREFIX, \"\"));\n    RegistryImage registryImage = RegistryImage.named(imageReference);\n    DefaultCredentialRetrievers defaultCredentialRetrievers =\n        DefaultCredentialRetrievers.init(\n            CredentialRetrieverFactory.forImage(\n                imageReference,\n                logEvent -> logger.log(logEvent.getLevel(), logEvent.getMessage())));\n    Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers)\n        .forEach(registryImage::addCredentialRetriever);\n    JibContainerBuilder containerBuilder = Jib.from(registryImage);\n    if (!platforms.isEmpty()) {\n      containerBuilder.setPlatforms(platforms);\n    }\n    return containerBuilder;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/Containerizers.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.cloud.tools.jib.api.Jib.DOCKER_DAEMON_IMAGE_PREFIX;\nimport static com.google.cloud.tools.jib.api.Jib.REGISTRY_IMAGE_PREFIX;\nimport static com.google.cloud.tools.jib.api.Jib.TAR_IMAGE_PREFIX;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.DockerDaemonImage;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.TarImage;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.cloud.tools.jib.event.progress.ProgressEventHandler;\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.plugins.common.DefaultCredentialRetrievers;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.ProgressDisplayGenerator;\nimport java.io.FileNotFoundException;\nimport java.nio.file.Paths;\nimport java.util.List;\n\n/** Helper class for creating Containerizers from JibCli specifications. */\npublic class Containerizers {\n\n  private Containerizers() {}\n\n  /**\n   * Create a Containerizer from a command line specification.\n   *\n   * @param commonCliOptions common cli options\n   * @param logger a logger to inject into the build\n   * @param cacheDirectories the location of the relevant caches for this build\n   * @return a populated Containerizer\n   * @throws InvalidImageReferenceException if the image reference could not be parsed\n   * @throws FileNotFoundException if a credential helper file is not found\n   */\n  public static Containerizer from(\n      CommonCliOptions commonCliOptions, ConsoleLogger logger, CacheDirectories cacheDirectories)\n      throws InvalidImageReferenceException, FileNotFoundException {\n    Containerizer containerizer = create(commonCliOptions, logger);\n\n    applyHandlers(containerizer, logger);\n    applyConfiguration(containerizer, commonCliOptions, cacheDirectories);\n\n    return containerizer;\n  }\n\n  private static Containerizer create(CommonCliOptions commonCliOptions, ConsoleLogger logger)\n      throws InvalidImageReferenceException, FileNotFoundException {\n    String imageSpec = commonCliOptions.getTargetImage();\n    if (imageSpec.startsWith(DOCKER_DAEMON_IMAGE_PREFIX)) {\n      // TODO: allow setting docker env and docker executable (along with path/env)\n      return Containerizer.to(\n          DockerDaemonImage.named(imageSpec.replaceFirst(DOCKER_DAEMON_IMAGE_PREFIX, \"\")));\n    }\n    if (imageSpec.startsWith(TAR_IMAGE_PREFIX)) {\n      return Containerizer.to(\n          TarImage.at(Paths.get(imageSpec.replaceFirst(TAR_IMAGE_PREFIX, \"\")))\n              .named(commonCliOptions.getName()));\n    }\n    ImageReference imageReference =\n        ImageReference.parse(imageSpec.replaceFirst(REGISTRY_IMAGE_PREFIX, \"\"));\n    RegistryImage registryImage = RegistryImage.named(imageReference);\n    DefaultCredentialRetrievers defaultCredentialRetrievers =\n        DefaultCredentialRetrievers.init(\n            CredentialRetrieverFactory.forImage(\n                imageReference,\n                logEvent -> logger.log(logEvent.getLevel(), logEvent.getMessage())));\n    Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers)\n        .forEach(registryImage::addCredentialRetriever);\n    return Containerizer.to(registryImage);\n  }\n\n  private static void applyConfiguration(\n      Containerizer containerizer,\n      CommonCliOptions commonCliOptions,\n      CacheDirectories cacheDirectories) {\n    containerizer.setToolName(VersionInfo.TOOL_NAME);\n    containerizer.setToolVersion(VersionInfo.getVersionSimple());\n\n    // TODO: it's strange that we use system properties to set these\n    // TODO: perhaps we should expose these as configuration options on the containerizer\n    if (commonCliOptions.isSendCredentialsOverHttp()) {\n      System.setProperty(JibSystemProperties.SEND_CREDENTIALS_OVER_HTTP, Boolean.TRUE.toString());\n    }\n    if (commonCliOptions.isSerialize()) {\n      System.setProperty(JibSystemProperties.SERIALIZE, Boolean.TRUE.toString());\n    }\n\n    containerizer.setAllowInsecureRegistries(commonCliOptions.isAllowInsecureRegistries());\n    cacheDirectories.getBaseImageCache().ifPresent(containerizer::setBaseImageLayersCache);\n    containerizer.setApplicationLayersCache(cacheDirectories.getApplicationLayersCache());\n\n    commonCliOptions.getAdditionalTags().forEach(containerizer::withAdditionalTag);\n  }\n\n  private static void applyHandlers(Containerizer containerizer, ConsoleLogger consoleLogger) {\n    containerizer\n        .addEventHandler(\n            LogEvent.class,\n            logEvent -> consoleLogger.log(logEvent.getLevel(), logEvent.getMessage()))\n        .addEventHandler(\n            ProgressEvent.class,\n            new ProgressEventHandler(\n                update -> {\n                  List<String> footer =\n                      ProgressDisplayGenerator.generateProgressDisplay(\n                          update.getProgress(), update.getUnfinishedLeafTasks());\n                  footer.add(\"\");\n                  consoleLogger.setFooter(footer);\n                }));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/Credentials.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.plugins.common.DefaultCredentialRetrievers;\nimport java.io.FileNotFoundException;\nimport java.util.List;\n\n/**\n * A helper class to process command line args and generate a list of {@link CredentialRetriever}s.\n */\npublic class Credentials {\n\n  private Credentials() {}\n\n  /**\n   * Gets credentials for a target image registry.\n   *\n   * @param commonCliOptions common cli options\n   * @param defaultCredentialRetrievers An initialized {@link DefaultCredentialRetrievers} to use\n   * @return a list of credentials for a target image registry\n   * @throws FileNotFoundException when a credential helper file cannot be found\n   */\n  public static List<CredentialRetriever> getToCredentialRetrievers(\n      CommonCliOptions commonCliOptions, DefaultCredentialRetrievers defaultCredentialRetrievers)\n      throws FileNotFoundException {\n    // these are all mutually exclusive as enforced by the CLI\n    commonCliOptions\n        .getUsernamePassword()\n        .ifPresent(\n            credential ->\n                defaultCredentialRetrievers.setKnownCredential(\n                    credential, \"--username/--password\"));\n    commonCliOptions\n        .getToUsernamePassword()\n        .ifPresent(\n            credential ->\n                defaultCredentialRetrievers.setKnownCredential(\n                    credential, \"--to-username/--to-password\"));\n    commonCliOptions\n        .getCredentialHelper()\n        .ifPresent(defaultCredentialRetrievers::setCredentialHelper);\n    commonCliOptions\n        .getToCredentialHelper()\n        .ifPresent(defaultCredentialRetrievers::setCredentialHelper);\n\n    return defaultCredentialRetrievers.asList();\n  }\n\n  /**\n   * Gets credentials for a base image registry.\n   *\n   * @param commonCliOptions common cli options\n   * @param defaultCredentialRetrievers An initialized {@link DefaultCredentialRetrievers} to use\n   * @return a list of credentials for a base image registry\n   * @throws FileNotFoundException when a credential helper file cannot be found\n   */\n  public static List<CredentialRetriever> getFromCredentialRetrievers(\n      CommonCliOptions commonCliOptions, DefaultCredentialRetrievers defaultCredentialRetrievers)\n      throws FileNotFoundException {\n    // these are all mutually exclusive as enforced by the CLI\n\n    commonCliOptions\n        .getUsernamePassword()\n        .ifPresent(\n            credential ->\n                defaultCredentialRetrievers.setKnownCredential(\n                    credential, \"--username/--password\"));\n    commonCliOptions\n        .getFromUsernamePassword()\n        .ifPresent(\n            credential ->\n                defaultCredentialRetrievers.setKnownCredential(\n                    credential, \"--from-username/--from-password\"));\n    commonCliOptions\n        .getCredentialHelper()\n        .ifPresent(defaultCredentialRetrievers::setCredentialHelper);\n    commonCliOptions\n        .getFromCredentialHelper()\n        .ifPresent(defaultCredentialRetrievers::setCredentialHelper);\n\n    return defaultCredentialRetrievers.asList();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/Instants.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeFormatterBuilder;\nimport java.time.format.DateTimeParseException;\n\n/** Helper class to convert various strings in a buildfile to Instants. */\npublic class Instants {\n\n  private Instants() {}\n\n  /**\n   * Parses a time string into Instant. The string must be time in milliseconds since unix epoch or\n   * an iso8601 datetime.\n   *\n   * @param time in milliseconds since epoch or iso8601 format\n   * @param fieldName name of field being parsed (for error messaging)\n   * @return Instant value of parsed time\n   */\n  public static Instant fromMillisOrIso8601(String time, String fieldName) {\n    try {\n      return Instant.ofEpochMilli(Long.parseLong(time));\n    } catch (NumberFormatException nfe) {\n      // TODO: copied from PluginConfigurationProcessor, find a way to share better\n      try {\n        DateTimeFormatter formatter =\n            new DateTimeFormatterBuilder()\n                .append(DateTimeFormatter.ISO_DATE_TIME)\n                .optionalStart()\n                .appendOffset(\"+HHmm\", \"+0000\")\n                .optionalEnd()\n                .toFormatter();\n        return formatter.parse(time, Instant::from);\n      } catch (DateTimeParseException dtpe) {\n        throw new IllegalArgumentException(\n            fieldName\n                + \" must be a number of milliseconds since epoch or an ISO 8601 formatted date\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/Jar.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.cli.jar.JarFiles;\nimport com.google.cloud.tools.jib.cli.jar.ProcessingMode;\nimport com.google.cloud.tools.jib.cli.logging.CliLogger;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.Multimaps;\nimport com.google.common.util.concurrent.Futures;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Future;\nimport picocli.CommandLine;\nimport picocli.CommandLine.Model.CommandSpec;\n\n@CommandLine.Command(\n    name = \"jar\",\n    mixinStandardHelpOptions = true,\n    showAtFileInUsageHelp = true,\n    description = \"Containerize a jar\")\npublic class Jar implements Callable<Integer> {\n\n  @CommandLine.Spec\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private CommandSpec spec;\n\n  @CommandLine.Mixin\n  @VisibleForTesting\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  CommonCliOptions commonCliOptions;\n\n  @CommandLine.Mixin\n  @VisibleForTesting\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  CommonContainerConfigCliOptions commonContainerConfigCliOptions;\n\n  @CommandLine.Parameters(description = \"The path to the jar file (ex: path/to/my-jar.jar)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Path jarFile;\n\n  @CommandLine.Option(\n      names = \"--mode\",\n      defaultValue = \"exploded\",\n      paramLabel = \"<mode>\",\n      description =\n          \"The jar processing mode, candidates: ${COMPLETION-CANDIDATES}, default: ${DEFAULT-VALUE}\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private ProcessingMode mode;\n\n  @CommandLine.Option(\n      names = \"--jvm-flags\",\n      paramLabel = \"<jvm-flag>\",\n      split = \",\",\n      description = \"JVM arguments, example: --jvm-flags=-Dmy.property=value,-Xshare:off\")\n  private List<String> jvmFlags = Collections.emptyList();\n\n  @Override\n  public Integer call() {\n    commonCliOptions.validate();\n    SingleThreadedExecutor executor = new SingleThreadedExecutor();\n    ConsoleLogger logger =\n        CliLogger.newLogger(\n            commonCliOptions.getVerbosity(),\n            commonCliOptions.getHttpTrace(),\n            commonCliOptions.getConsoleOutput(),\n            spec.commandLine().getOut(),\n            spec.commandLine().getErr(),\n            executor);\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());\n    try {\n      JibCli.configureHttpLogging(commonCliOptions.getHttpTrace().toJulLevel());\n      GlobalConfig globalConfig = GlobalConfig.readConfig();\n      updateCheckFuture =\n          JibCli.newUpdateChecker(\n              globalConfig,\n              commonCliOptions.getVerbosity(),\n              logEvent -> logger.log(logEvent.getLevel(), logEvent.getMessage()));\n      if (!Files.exists(jarFile)) {\n        logger.log(LogEvent.Level.ERROR, \"The file path provided does not exist: \" + jarFile);\n        return 1;\n      }\n      if (Files.isDirectory(jarFile)) {\n        logger.log(\n            LogEvent.Level.ERROR,\n            \"The file path provided is for a directory. Please provide a path to a JAR: \"\n                + jarFile);\n        return 1;\n      }\n      if (!commonContainerConfigCliOptions.getEntrypoint().isEmpty() && !jvmFlags.isEmpty()) {\n        logger.log(LogEvent.Level.WARN, \"--jvm-flags is ignored when --entrypoint is specified\");\n      }\n\n      Path jarFileParentDir = Verify.verifyNotNull(jarFile.toAbsolutePath().getParent());\n      CacheDirectories cacheDirectories = CacheDirectories.from(commonCliOptions, jarFileParentDir);\n      ArtifactProcessor processor =\n          ArtifactProcessors.fromJar(\n              jarFile, cacheDirectories, this, commonContainerConfigCliOptions);\n      JibContainerBuilder containerBuilder =\n          JarFiles.toJibContainerBuilder(\n              processor, this, commonCliOptions, commonContainerConfigCliOptions, logger);\n      Containerizer containerizer = Containerizers.from(commonCliOptions, logger, cacheDirectories);\n\n      // Enable registry mirrors\n      Multimaps.asMap(globalConfig.getRegistryMirrors()).forEach(containerizer::addRegistryMirrors);\n\n      JibContainer jibContainer = containerBuilder.containerize(containerizer);\n      JibCli.writeImageJson(commonCliOptions.getImageJsonPath(), jibContainer);\n    } catch (InterruptedException ex) {\n      JibCli.logTerminatingException(logger, ex, commonCliOptions.isStacktrace());\n      Thread.currentThread().interrupt();\n      return 1;\n    } catch (Exception ex) {\n      JibCli.logTerminatingException(logger, ex, commonCliOptions.isStacktrace());\n      return 1;\n    } finally {\n      JibCli.finishUpdateChecker(logger, updateCheckFuture);\n      executor.shutDownAndAwaitTermination(Duration.ofSeconds(3));\n    }\n    return 0;\n  }\n\n  public List<String> getJvmFlags() {\n    return jvmFlags;\n  }\n\n  public ProcessingMode getMode() {\n    return mode;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/JibCli.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.api.client.http.HttpTransport;\nimport com.google.api.client.http.apache.v2.ApacheHttpTransport;\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.cli.logging.Verbosity;\nimport com.google.cloud.tools.jib.plugins.common.ImageMetadataOutput;\nimport com.google.cloud.tools.jib.plugins.common.UpdateChecker;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.function.Consumer;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport picocli.CommandLine;\n\n@CommandLine.Command(\n    name = \"jib\",\n    versionProvider = VersionInfo.class,\n    mixinStandardHelpOptions = true,\n    showAtFileInUsageHelp = true,\n    synopsisSubcommandLabel = \"COMMAND\",\n    description = \"A tool for creating container images\",\n    subcommands = {Build.class, Jar.class, War.class})\npublic class JibCli {\n\n  private static final String VERSION_URL = \"https://storage.googleapis.com/jib-versions/jib-cli\";\n\n  static Logger configureHttpLogging(Level level) {\n    // To instantiate the static HttpTransport logger field.\n    // Fixes https://github.com/GoogleContainerTools/jib/issues/3156.\n    new ApacheHttpTransport();\n    ConsoleHandler consoleHandler = new ConsoleHandler();\n    consoleHandler.setLevel(level);\n\n    Logger logger = Logger.getLogger(HttpTransport.class.getName());\n    logger.setLevel(level);\n    logger.addHandler(consoleHandler);\n    return logger;\n  }\n\n  static void logTerminatingException(\n      ConsoleLogger consoleLogger, Exception exception, boolean logStackTrace) {\n    if (logStackTrace) {\n      StringWriter writer = new StringWriter();\n      exception.printStackTrace(new PrintWriter(writer));\n      consoleLogger.log(LogEvent.Level.ERROR, writer.toString());\n    }\n\n    consoleLogger.log(\n        LogEvent.Level.ERROR,\n        \"\\u001B[31;1m\"\n            + exception.getClass().getName()\n            + \": \"\n            + exception.getMessage()\n            + \"\\u001B[0m\");\n  }\n\n  static Future<Optional<String>> newUpdateChecker(\n      GlobalConfig globalConfig, Verbosity verbosity, Consumer<LogEvent> log) {\n    if (!verbosity.atLeast(Verbosity.info) || globalConfig.isDisableUpdateCheck()) {\n      return Futures.immediateFuture(Optional.empty());\n    }\n    ExecutorService executorService = Executors.newSingleThreadExecutor();\n    try {\n      return UpdateChecker.checkForUpdate(\n          executorService, VERSION_URL, VersionInfo.TOOL_NAME, VersionInfo.getVersionSimple(), log);\n    } finally {\n      executorService.shutdown();\n    }\n  }\n\n  static void finishUpdateChecker(\n      ConsoleLogger logger, Future<Optional<String>> updateCheckFuture) {\n    UpdateChecker.finishUpdateCheck(updateCheckFuture)\n        .ifPresent(\n            latestVersion -> {\n              String cliReleaseUrl =\n                  ProjectInfo.GITHUB_URL + \"/releases/tag/v\" + latestVersion + \"-cli\";\n              String changelogUrl = ProjectInfo.GITHUB_URL + \"/blob/master/jib-cli/CHANGELOG.md\";\n              String privacyUrl = ProjectInfo.GITHUB_URL + \"/blob/master/docs/privacy.md\";\n              String message =\n                  String.format(\n                      \"\\n\\u001B[33mA new version of Jib CLI (%s) is available (currently using %s). Download the latest\"\n                          + \" Jib CLI version from %s\\n%s\\u001B[0m\\n\\nPlease see %s for info on disabling this update check.\\n\",\n                      latestVersion,\n                      VersionInfo.getVersionSimple(),\n                      cliReleaseUrl,\n                      changelogUrl,\n                      privacyUrl);\n              logger.log(LogEvent.Level.LIFECYCLE, message);\n            });\n  }\n\n  /**\n   * Writes image details (imageId, digest, tags, etc.) to a json file, if the path to the json is\n   * provided.\n   *\n   * @param imageJsonOutputPath optional path to json file (for example,\n   *     path/to/json/jib-image.json)\n   * @param jibContainer the {@link JibContainer} to derive image details from\n   * @throws IOException if error occurs when writing to the json file.\n   */\n  static void writeImageJson(Optional<Path> imageJsonOutputPath, JibContainer jibContainer)\n      throws IOException {\n    if (imageJsonOutputPath.isPresent()) {\n      ImageMetadataOutput metadataOutput = ImageMetadataOutput.fromJibContainer(jibContainer);\n      Files.write(\n          imageJsonOutputPath.get(), metadataOutput.toJson().getBytes(StandardCharsets.UTF_8));\n    }\n  }\n\n  /**\n   * The magic starts here.\n   *\n   * @param args the command-line arguments\n   */\n  public static void main(String[] args) {\n    int exitCode =\n        new CommandLine(new JibCli())\n            .setParameterExceptionHandler(new ShortErrorMessageHandler())\n            .execute(args);\n    System.exit(exitCode);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ShortErrorMessageHandler.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport java.io.PrintWriter;\nimport picocli.CommandLine;\nimport picocli.CommandLine.IParameterExceptionHandler;\nimport picocli.CommandLine.Model.CommandSpec;\nimport picocli.CommandLine.ParameterException;\n\n/** Class to print a short error message when an invalid input is passed in. */\npublic class ShortErrorMessageHandler implements IParameterExceptionHandler {\n\n  @Override\n  public int handleParseException(ParameterException exception, String[] args) {\n    CommandLine command = exception.getCommandLine();\n    PrintWriter writer = command.getErr();\n\n    // Print error message\n    writer.println(exception.getMessage());\n    CommandLine.UnmatchedArgumentException.printSuggestions(exception, writer);\n    writer.print(command.getHelp().fullSynopsis());\n\n    CommandSpec commandSpec = command.getCommandSpec();\n    writer.printf(\"Run '%s --help' for more information on usage.%n\", commandSpec.qualifiedName());\n    return command.getExitCodeExceptionMapper() != null\n        ? command.getExitCodeExceptionMapper().getExitCode(exception)\n        : commandSpec.exitCodeOnInvalidInput();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/War.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.cli.logging.CliLogger;\nimport com.google.cloud.tools.jib.cli.war.WarFiles;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.Multimaps;\nimport com.google.common.util.concurrent.Futures;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Future;\nimport picocli.CommandLine;\n\n@CommandLine.Command(\n    name = \"war\",\n    mixinStandardHelpOptions = true,\n    showAtFileInUsageHelp = true,\n    description = \"Containerize a war\")\npublic class War implements Callable<Integer> {\n  @CommandLine.Spec\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private CommandLine.Model.CommandSpec spec;\n\n  @CommandLine.Mixin\n  @VisibleForTesting\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  CommonCliOptions commonCliOptions;\n\n  @CommandLine.Mixin\n  @VisibleForTesting\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  CommonContainerConfigCliOptions commonContainerConfigCliOptions;\n\n  @CommandLine.Parameters(description = \"The path to the war file (ex: path/to/my-war.war)\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Path warFile;\n\n  @CommandLine.Option(\n      names = \"--app-root\",\n      paramLabel = \"<app root>\",\n      description = \"The app root on the container\")\n  @SuppressWarnings(\"NullAway.Init\") // initialized by picocli\n  private Path appRoot;\n\n  @Override\n  public Integer call() {\n    commonCliOptions.validate();\n    SingleThreadedExecutor executor = new SingleThreadedExecutor();\n    ConsoleLogger logger =\n        CliLogger.newLogger(\n            commonCliOptions.getVerbosity(),\n            commonCliOptions.getHttpTrace(),\n            commonCliOptions.getConsoleOutput(),\n            spec.commandLine().getOut(),\n            spec.commandLine().getErr(),\n            executor);\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());\n    try {\n      JibCli.configureHttpLogging(commonCliOptions.getHttpTrace().toJulLevel());\n      GlobalConfig globalConfig = GlobalConfig.readConfig();\n      updateCheckFuture =\n          JibCli.newUpdateChecker(\n              globalConfig,\n              commonCliOptions.getVerbosity(),\n              logEvent -> logger.log(logEvent.getLevel(), logEvent.getMessage()));\n      if (!Files.exists(warFile)) {\n        logger.log(LogEvent.Level.ERROR, \"The file path provided does not exist: \" + warFile);\n        return 1;\n      }\n      if (Files.isDirectory(warFile)) {\n        logger.log(\n            LogEvent.Level.ERROR,\n            \"The file path provided is for a directory. Please provide a path to a WAR: \"\n                + warFile);\n        return 1;\n      }\n\n      Path warFileParentDir = Verify.verifyNotNull(warFile.toAbsolutePath().getParent());\n      CacheDirectories cacheDirectories = CacheDirectories.from(commonCliOptions, warFileParentDir);\n      ArtifactProcessor processor =\n          ArtifactProcessors.fromWar(\n              warFile, cacheDirectories, this, commonContainerConfigCliOptions);\n      JibContainerBuilder containerBuilder =\n          WarFiles.toJibContainerBuilder(\n              processor, commonCliOptions, commonContainerConfigCliOptions, logger);\n      Containerizer containerizer = Containerizers.from(commonCliOptions, logger, cacheDirectories);\n\n      // Enable registry mirrors\n      Multimaps.asMap(globalConfig.getRegistryMirrors()).forEach(containerizer::addRegistryMirrors);\n\n      JibContainer jibContainer = containerBuilder.containerize(containerizer);\n      JibCli.writeImageJson(commonCliOptions.getImageJsonPath(), jibContainer);\n    } catch (InterruptedException ex) {\n      JibCli.logTerminatingException(logger, ex, commonCliOptions.isStacktrace());\n      Thread.currentThread().interrupt();\n      return 1;\n    } catch (Exception ex) {\n      JibCli.logTerminatingException(logger, ex, commonCliOptions.isStacktrace());\n      return 1;\n    } finally {\n      JibCli.finishUpdateChecker(logger, updateCheckFuture);\n      executor.shutDownAndAwaitTermination(Duration.ofSeconds(3));\n    }\n    return 0;\n  }\n\n  /**\n   * Returns the user-specified app root in the container.\n   *\n   * @return a user configured app root\n   */\n  public Optional<AbsoluteUnixPath> getAppRoot() {\n    if (appRoot == null) {\n      return Optional.empty();\n    }\n    return Optional.of(AbsoluteUnixPath.fromPath(appRoot));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/ArchiveLayerSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * A yaml block for specifying archive layers.\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * name: \"my tar layer\"\n * archive: output/mytar.tgz\n * // optional mediatype\n * mediaType: application/vnd.docker.image.rootfs.diff.tar.gzip\n * }</pre>\n */\n@JsonDeserialize(using = JsonDeserializer.None.class) // required since LayerSpec overrides this\npublic class ArchiveLayerSpec implements LayerSpec {\n\n  private final String name;\n  // TODO: arhive should maybe be a uri to support file paths or urls\n  private final Path archive;\n  @Nullable private final String mediaType;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param name a unique name for this layer\n   * @param archive a path to an archive file\n   * @param mediaType the media type of the file\n   */\n  @JsonCreator\n  public ArchiveLayerSpec(\n      @JsonProperty(value = \"name\", required = true) String name,\n      @JsonProperty(value = \"archive\", required = true) String archive,\n      @JsonProperty(\"mediaType\") String mediaType) {\n    Validator.checkNotNullAndNotEmpty(name, \"name\");\n    Validator.checkNotNullAndNotEmpty(archive, \"archive\");\n    Validator.checkNullOrNotEmpty(mediaType, \"mediaType\");\n    this.name = name;\n    this.archive = Paths.get(archive);\n    this.mediaType = mediaType;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public Path getArchive() {\n    return archive;\n  }\n\n  public Optional<String> getMediaType() {\n    return Optional.ofNullable(mediaType);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/BaseImageSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.common.collect.ImmutableList;\nimport java.util.List;\n\n/**\n * A yaml block for specifying a base image with support for multi platform selections.\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * image: gcr.io/example/baseimage\n * platforms:\n *   - see }{@link PlatformSpec}{@code\n *   - see }{@link PlatformSpec}{@code\n * }</pre>\n */\npublic class BaseImageSpec {\n  private final String image;\n  private final List<PlatformSpec> platforms;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param image an image reference for a base image\n   * @param platforms a list of platforms when using a manifest list or image index\n   */\n  @JsonCreator\n  public BaseImageSpec(\n      @JsonProperty(value = \"image\", required = true) String image,\n      @JsonProperty(\"platforms\") List<PlatformSpec> platforms) {\n    Validator.checkNotNullAndNotEmpty(image, \"image\");\n    Validator.checkNullOrNonNullEntries(platforms, \"platforms\");\n    this.image = image;\n    this.platforms = platforms == null ? ImmutableList.of() : platforms;\n  }\n\n  public String getImage() {\n    return image;\n  }\n\n  public List<PlatformSpec> getPlatforms() {\n    return platforms;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/BuildFileSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.api.Ports;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.cli.Instants;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\n/**\n * A yaml block for specifying a jib cli buildfile.\n *\n * <p>Example use of this yaml.\n *\n * <pre>{@code\n * apiVersion: v1alpha1\n * kind: BuildFile\n * from: see }{@link BaseImageSpec}{@code\n * creationTime: 100\n * format: docker\n * environment:\n *   env_key: env_value\n * labels:\n *   label_key: label_value\n * volumes:\n *   - /my/volume\n * exposedPorts:\n *   - 8080\n * user: username\n * workingDirectory: /workspace\n * entrypoint:\n *   - java\n *   - -jar\n * cmd:\n *   - myjar.jar\n * layers: see }{@link LayersSpec}{@code\n * }</pre>\n */\npublic class BuildFileSpec {\n  private final String apiVersion;\n  private final String kind;\n  @Nullable private final BaseImageSpec from;\n  @Nullable private final Instant creationTime;\n  @Nullable private final ImageFormat format;\n  private final Map<String, String> environment;\n  private final Map<String, String> labels;\n  private final Set<AbsoluteUnixPath> volumes;\n  private final Set<Port> exposedPorts;\n  @Nullable private final String user;\n  @Nullable private final AbsoluteUnixPath workingDirectory;\n  /**\n   * Entrypoint has special behavior as a nullable list. When null, it delegates to the existing\n   * base image entrypoint. If non null (including empty) it overwrites the base image entrypoint.\n   */\n  @Nullable private final List<String> entrypoint;\n  /**\n   * Cmd has special behavior as a nullable list. When null, it delegates to the existing base image\n   * cmd. If non null (including empty) it overwrites the base image cmd.\n   */\n  @Nullable private final List<String> cmd;\n\n  @Nullable private final LayersSpec layers;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param apiVersion the api version of this buildfile\n   * @param kind the type of configuration file (always BuildFile)\n   * @param from a {@link BaseImageSpec} for specifying the base image\n   * @param creationTime in milliseconds since epoch or ISO 8601 datetime\n   * @param format of the container, valid values in {@link ImageFormat}\n   * @param environment to write into container\n   * @param labels to write into container metadata\n   * @param volumes directories on container that may hold external volumes\n   * @param exposedPorts a set of ports to expose on the container\n   * @param user the username or id to run the container\n   * @param workingDirectory an absolute path to the default working directory\n   * @param entrypoint the container entry point\n   * @param cmd the container entrypoint command arguments\n   * @param layers a list of {@link LayerSpec} that define the container filesystem\n   */\n  @JsonCreator\n  public BuildFileSpec(\n      @JsonProperty(value = \"apiVersion\", required = true) String apiVersion,\n      @JsonProperty(value = \"kind\", required = true) String kind,\n      @JsonProperty(\"from\") BaseImageSpec from,\n      @JsonProperty(\"creationTime\") String creationTime,\n      @JsonProperty(\"format\") String format,\n      @JsonProperty(\"environment\") Map<String, String> environment,\n      @JsonProperty(\"labels\") Map<String, String> labels,\n      @JsonProperty(\"volumes\") Set<String> volumes,\n      @JsonProperty(\"exposedPorts\") List<String> exposedPorts,\n      @JsonProperty(\"user\") String user,\n      @JsonProperty(\"workingDirectory\") String workingDirectory,\n      @JsonProperty(\"entrypoint\") List<String> entrypoint,\n      @JsonProperty(\"cmd\") List<String> cmd,\n      @JsonProperty(\"layers\") LayersSpec layers) {\n\n    Validator.checkNotNullAndNotEmpty(apiVersion, \"apiVersion\");\n    Validator.checkEquals(kind, \"kind\", \"BuildFile\");\n\n    Validator.checkNullOrNotEmpty(creationTime, \"creationTime\");\n    Validator.checkNullOrNotEmpty(format, \"format\");\n    Validator.checkNullOrNonNullNonEmptyEntries(environment, \"environment\");\n    Validator.checkNullOrNonNullNonEmptyEntries(labels, \"labels\");\n    Validator.checkNullOrNonNullNonEmptyEntries(volumes, \"volumes\");\n    Validator.checkNullOrNonNullNonEmptyEntries(exposedPorts, \"exposedPorts\");\n    Validator.checkNullOrNotEmpty(user, \"user\");\n    Validator.checkNullOrNotEmpty(workingDirectory, \"workingDirectory\");\n    Validator.checkNullOrNonNullNonEmptyEntries(entrypoint, \"entrypoint\");\n    Validator.checkNullOrNonNullNonEmptyEntries(cmd, \"cmd\");\n\n    this.apiVersion = apiVersion;\n    Preconditions.checkArgument(\n        \"BuildFile\".equals(kind), \"Field 'kind' must be BuildFile but is \" + kind);\n    this.kind = kind;\n    this.from = from;\n    this.creationTime =\n        (creationTime == null) ? null : Instants.fromMillisOrIso8601(creationTime, \"creationTime\");\n    this.format = (format == null) ? null : ImageFormat.valueOf(format);\n    this.environment = (environment == null) ? ImmutableMap.of() : environment;\n    this.labels = (labels == null) ? ImmutableMap.of() : labels;\n    this.volumes =\n        (volumes == null)\n            ? ImmutableSet.of()\n            : volumes.stream().map(AbsoluteUnixPath::get).collect(Collectors.toSet());\n    this.exposedPorts = (exposedPorts == null) ? ImmutableSet.of() : Ports.parse(exposedPorts);\n    this.user = user;\n    this.workingDirectory =\n        (workingDirectory == null) ? null : AbsoluteUnixPath.get(workingDirectory);\n    this.entrypoint = entrypoint;\n    this.cmd = cmd;\n    this.layers = layers;\n  }\n\n  public String getApiVersion() {\n    return apiVersion;\n  }\n\n  public String getKind() {\n    return kind;\n  }\n\n  public Optional<BaseImageSpec> getFrom() {\n    return Optional.ofNullable(from);\n  }\n\n  public Optional<Instant> getCreationTime() {\n    return Optional.ofNullable(creationTime);\n  }\n\n  public Optional<ImageFormat> getFormat() {\n    return Optional.ofNullable(format);\n  }\n\n  public Map<String, String> getEnvironment() {\n    return environment;\n  }\n\n  public Map<String, String> getLabels() {\n    return labels;\n  }\n\n  public Set<AbsoluteUnixPath> getVolumes() {\n    return volumes;\n  }\n\n  public Set<Port> getExposedPorts() {\n    return exposedPorts;\n  }\n\n  public Optional<String> getUser() {\n    return Optional.ofNullable(user);\n  }\n\n  public Optional<AbsoluteUnixPath> getWorkingDirectory() {\n    return Optional.ofNullable(workingDirectory);\n  }\n\n  public Optional<List<String>> getEntrypoint() {\n    return Optional.ofNullable(entrypoint);\n  }\n\n  public Optional<List<String>> getCmd() {\n    return Optional.ofNullable(cmd);\n  }\n\n  public Optional<LayersSpec> getLayers() {\n    return Optional.ofNullable(layers);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/BuildFiles.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.cli.Build;\nimport com.google.cloud.tools.jib.cli.CommonCliOptions;\nimport com.google.cloud.tools.jib.cli.ContainerBuilders;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.base.Charsets;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport org.apache.commons.text.StringSubstitutor;\nimport org.apache.commons.text.io.StringSubstitutorReader;\n\n/** Class to convert BuildFiles to build container representations. */\npublic class BuildFiles {\n\n  private BuildFiles() {}\n\n  /** Read a build file from disk and apply templating parameters. */\n  private static BuildFileSpec toBuildFileSpec(\n      Path buildFilePath, Map<String, String> templateParameters) throws IOException {\n    ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory());\n    StringSubstitutor templater =\n        new StringSubstitutor(templateParameters).setEnableUndefinedVariableException(true);\n    try (StringSubstitutorReader reader =\n        new StringSubstitutorReader(\n            Files.newBufferedReader(buildFilePath, Charsets.UTF_8), templater)) {\n      return yamlObjectMapper.readValue(reader, BuildFileSpec.class);\n    }\n  }\n\n  /**\n   * Read a buildfile from disk and generate a JibContainerBuilder instance. All parsing of files\n   * considers the directory the buildfile is located in as the working directory.\n   *\n   * @param projectRoot the root context directory of this build\n   * @param buildFilePath a file containing the build definition\n   * @param buildCommandOptions the build configuration from the command line\n   * @param commonCliOptions common cli options\n   * @param logger a logger to inject into various objects that do logging\n   * @return a {@link JibContainerBuilder} generated from the contents of {@code buildFilePath}\n   * @throws IOException if an I/O error occurs opening the file, or an error occurs while\n   *     traversing files on the filesystem\n   * @throws InvalidImageReferenceException if the baseImage reference can not be parsed\n   */\n  public static JibContainerBuilder toJibContainerBuilder(\n      Path projectRoot,\n      Path buildFilePath,\n      Build buildCommandOptions,\n      CommonCliOptions commonCliOptions,\n      ConsoleLogger logger)\n      throws InvalidImageReferenceException, IOException {\n    BuildFileSpec buildFile =\n        toBuildFileSpec(buildFilePath, buildCommandOptions.getTemplateParameters());\n    Optional<BaseImageSpec> baseImageSpec = buildFile.getFrom();\n    JibContainerBuilder containerBuilder =\n        baseImageSpec.isPresent()\n            ? createJibContainerBuilder(baseImageSpec.get(), commonCliOptions, logger)\n            : Jib.fromScratch();\n\n    buildFile.getCreationTime().ifPresent(containerBuilder::setCreationTime);\n    buildFile.getFormat().ifPresent(containerBuilder::setFormat);\n    containerBuilder.setEnvironment(buildFile.getEnvironment());\n    containerBuilder.setLabels(buildFile.getLabels());\n    containerBuilder.setVolumes(buildFile.getVolumes());\n    containerBuilder.setExposedPorts(buildFile.getExposedPorts());\n    buildFile.getUser().ifPresent(containerBuilder::setUser);\n    buildFile.getWorkingDirectory().ifPresent(containerBuilder::setWorkingDirectory);\n    buildFile.getEntrypoint().ifPresent(containerBuilder::setEntrypoint);\n    buildFile.getCmd().ifPresent(containerBuilder::setProgramArguments);\n\n    Optional<LayersSpec> layersSpec = buildFile.getLayers();\n    if (layersSpec.isPresent()) {\n      containerBuilder.setFileEntriesLayers(Layers.toLayers(projectRoot, layersSpec.get()));\n    }\n    return containerBuilder;\n  }\n\n  private static JibContainerBuilder createJibContainerBuilder(\n      BaseImageSpec baseImageSpec, CommonCliOptions commonCliOptions, ConsoleLogger logger)\n      throws InvalidImageReferenceException, FileNotFoundException {\n    LinkedHashSet<Platform> platforms =\n        baseImageSpec.getPlatforms().stream()\n            .map(platformSpec -> new Platform(platformSpec.getArchitecture(), platformSpec.getOs()))\n            .collect(Collectors.toCollection(LinkedHashSet::new));\n    return ContainerBuilders.create(baseImageSpec.getImage(), platforms, commonCliOptions, logger);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/CopySpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.common.collect.ImmutableList;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * A yaml block for specifying a copy directive.\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * src: path/to/somewhere\n * dest: /absolute/path/on/container\n * includes:\n *   - **\\/*.txt\n * excludes:\n *   - **\\/goose.txt\n *   - **\\/moose.txt\n * properties: see }{@link FilePropertiesSpec}{@code\n * }</pre>\n */\npublic class CopySpec {\n  private final Path src;\n  private final AbsoluteUnixPath dest;\n  // extra metadata to determine if the target can be considered a file\n  private final boolean destEndsWithSlash;\n\n  @Nullable private final FilePropertiesSpec properties;\n  private final List<String> excludes;\n  private final List<String> includes;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param src a file/directory on the local filesystem to copy *from*\n   * @param dest an absolute unix style path to copy *to* on the container\n   * @param includes glob to filter files to include from \"src\"\n   * @param excludes glob to filter out files included by \"src\" and \"includes\" (applied last)\n   * @param properties a {@link FilePropertiesSpec} that applies to all files in this copy\n   */\n  @JsonCreator\n  public CopySpec(\n      @JsonProperty(value = \"src\", required = true) String src,\n      @JsonProperty(value = \"dest\", required = true) String dest,\n      @JsonProperty(\"includes\") List<String> includes,\n      @JsonProperty(\"excludes\") List<String> excludes,\n      @JsonProperty(\"properties\") FilePropertiesSpec properties) {\n    Validator.checkNotNullAndNotEmpty(src, \"src\");\n    Validator.checkNotNullAndNotEmpty(dest, \"dest\");\n    Validator.checkNullOrNonNullNonEmptyEntries(includes, \"includes\");\n    Validator.checkNullOrNonNullNonEmptyEntries(excludes, \"excludes\");\n    this.src = Paths.get(src);\n    this.dest = AbsoluteUnixPath.get(dest);\n    this.destEndsWithSlash = dest.endsWith(\"/\");\n    this.excludes = (excludes == null) ? ImmutableList.of() : excludes;\n    this.includes = (includes == null) ? ImmutableList.of() : includes;\n    this.properties = properties;\n  }\n\n  public Path getSrc() {\n    return src;\n  }\n\n  public AbsoluteUnixPath getDest() {\n    return dest;\n  }\n\n  public boolean isDestEndsWithSlash() {\n    return destEndsWithSlash;\n  }\n\n  public List<String> getExcludes() {\n    return excludes;\n  }\n\n  public List<String> getIncludes() {\n    return includes;\n  }\n\n  public Optional<FilePropertiesSpec> getProperties() {\n    return Optional.ofNullable(properties);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/FileLayerSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * A yaml block for specifying files layers.\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * name: \"my classes layer\"\n * files:\n *   - }{@link CopySpec}{@code\n *   - }{@link CopySpec}{@code\n * // optional properties\n * properties: see }{@link FilePropertiesSpec}{@code\n * }</pre>\n */\n@JsonDeserialize(using = JsonDeserializer.None.class) // required since LayerSpec overrides this\npublic class FileLayerSpec implements LayerSpec {\n  private final String name;\n  private final List<CopySpec> files;\n  @Nullable private final FilePropertiesSpec properties;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param name a unique name for this layer\n   * @param files a list of {@link CopySpec} describing files to add to the layer\n   * @param properties a {@link FilePropertiesSpec} that applies to all files in this layer\n   */\n  @JsonCreator\n  public FileLayerSpec(\n      @JsonProperty(value = \"name\", required = true) String name,\n      @JsonProperty(value = \"files\", required = true) List<CopySpec> files,\n      @JsonProperty(\"properties\") FilePropertiesSpec properties) {\n    Validator.checkNotNullAndNotEmpty(name, \"name\");\n    Validator.checkNotNullAndNotEmpty(files, \"files\");\n    this.name = name;\n    this.properties = properties;\n    this.files = files;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public List<CopySpec> getFiles() {\n    return files;\n  }\n\n  public Optional<FilePropertiesSpec> getProperties() {\n    return Optional.ofNullable(properties);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.cli.Instants;\nimport java.time.Instant;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * A yaml block for specifying file properties (used in conjunction with layers).\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * filePermissions: 644\n * directoryPermissions: 755\n * user: foo\n * group: bar\n * timestamp: 100\n * }</pre>\n */\npublic class FilePropertiesSpec {\n  @Nullable private final FilePermissions filePermissions;\n  @Nullable private final FilePermissions directoryPermissions;\n  @Nullable private final String user;\n  @Nullable private final String group;\n  @Nullable private final Instant timestamp;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param filePermissions octal string for file permissions\n   * @param directoryPermissions octal string for directory permissions\n   * @param user name or number for ownership user\n   * @param group name or number for ownership group\n   * @param timestamp in milliseconds since epoch or ISO 8601 datetime\n   */\n  @JsonCreator\n  public FilePropertiesSpec(\n      @JsonProperty(\"filePermissions\") String filePermissions,\n      @JsonProperty(\"directoryPermissions\") String directoryPermissions,\n      @JsonProperty(\"user\") String user,\n      @JsonProperty(\"group\") String group,\n      @JsonProperty(\"timestamp\") String timestamp) {\n    Validator.checkNullOrNotEmpty(filePermissions, \"filePermissions\");\n    Validator.checkNullOrNotEmpty(directoryPermissions, \"directoryPermissions\");\n    Validator.checkNullOrNotEmpty(user, \"user\");\n    Validator.checkNullOrNotEmpty(group, \"group\");\n    Validator.checkNullOrNotEmpty(timestamp, \"timestamp\");\n    this.filePermissions =\n        filePermissions == null ? null : FilePermissions.fromOctalString(filePermissions);\n    this.directoryPermissions =\n        directoryPermissions == null ? null : FilePermissions.fromOctalString(directoryPermissions);\n    this.user = user;\n    this.group = group;\n    this.timestamp =\n        (timestamp == null) ? null : Instants.fromMillisOrIso8601(timestamp, \"timestamp\");\n  }\n\n  public Optional<FilePermissions> getFilePermissions() {\n    return Optional.ofNullable(filePermissions);\n  }\n\n  public Optional<FilePermissions> getDirectoryPermissions() {\n    return Optional.ofNullable(directoryPermissions);\n  }\n\n  public Optional<String> getUser() {\n    return Optional.ofNullable(user);\n  }\n\n  public Optional<String> getGroup() {\n    return Optional.ofNullable(group);\n  }\n\n  public Optional<Instant> getTimestamp() {\n    return Optional.ofNullable(timestamp);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesStack.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.common.base.Preconditions;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A class that keeps track of permissions for various stacking file permissions settings in {@link\n * LayerSpec}.\n */\nclass FilePropertiesStack {\n\n  // TODO perhaps use a fixed size list here\n  private final List<FilePropertiesSpec> stack = new ArrayList<>(3);\n\n  private FilePermissions filePermissions;\n  private FilePermissions directoryPermissions;\n  private Instant modificationTime;\n  private String ownership;\n\n  /** Create new FilePropertiesStack with defaults. */\n  public FilePropertiesStack() {\n    setDefaults();\n  }\n\n  private void setDefaults() {\n    filePermissions = FilePermissions.DEFAULT_FILE_PERMISSIONS;\n    directoryPermissions = FilePermissions.DEFAULT_FOLDER_PERMISSIONS;\n    modificationTime = FileEntriesLayer.DEFAULT_MODIFICATION_TIME;\n    // TODO: get default from FileEntriesLayer (requires buildplan release)\n    ownership = \"\";\n  }\n\n  /**\n   * Add a new layer to the file properties stack. When adding a new layer, it is given highest\n   * priority when resolving properties. All values are recalculated.\n   */\n  public void push(FilePropertiesSpec filePropertiesSpec) {\n    Preconditions.checkState(\n        stack.size() < 3, \"Error in file properties stack push, stacking over 3\");\n    stack.add(filePropertiesSpec);\n    updateProperties();\n  }\n\n  /** Remove the last layer from the stack. All values are recalculated. */\n  public void pop() {\n    Preconditions.checkState(!stack.isEmpty(), \"Error in file properties stack pop, popping at 0\");\n    stack.remove(stack.size() - 1);\n    updateProperties();\n  }\n\n  private void updateProperties() {\n    // clear existing permissions before recalculating\n    setDefaults();\n\n    String user = null;\n    String group = null;\n\n    // the item with the lowest index has the lowest priority\n    for (FilePropertiesSpec properties : stack) {\n      filePermissions = properties.getFilePermissions().orElse(filePermissions);\n      directoryPermissions = properties.getDirectoryPermissions().orElse(directoryPermissions);\n      modificationTime = properties.getTimestamp().orElse(modificationTime);\n      user = properties.getUser().orElse(user);\n      group = properties.getGroup().orElse(group);\n    }\n    // ownership calculations\n    ownership = (user != null ? user : \"\") + (group != null ? \":\" + group : \"\");\n  }\n\n  public FilePermissions getFilePermissions() {\n    return filePermissions;\n  }\n\n  public FilePermissions getDirectoryPermissions() {\n    return directoryPermissions;\n  }\n\n  public Instant getModificationTime() {\n    return modificationTime;\n  }\n\n  public String getOwnership() {\n    return ownership;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/LayerSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport com.fasterxml.jackson.databind.deser.std.StdDeserializer;\nimport java.io.IOException;\n\n/**\n * Polymorphic yaml LayerSpec interface with custom deserializer, can parse both {@link\n * ArchiveLayerSpec} and {@link FileLayerSpec}.\n */\n@JsonDeserialize(using = LayerSpec.Deserializer.class)\npublic interface LayerSpec {\n  class Deserializer extends StdDeserializer<LayerSpec> {\n\n    public Deserializer() {\n      super(LayerSpec.class);\n    }\n\n    /**\n     * Deserializes to {@link ArchiveLayerSpec} if yaml contains \"archive\" field, to {@link\n     * FileLayerSpec} if yaml contains \"files\" field or throws {@link IOException} if neither is\n     * found or no \"name\" was specified for the layer entry.\n     */\n    @Override\n    public LayerSpec deserialize(JsonParser jsonParser, DeserializationContext context)\n        throws IOException {\n      JsonNode node = jsonParser.getCodec().readTree(jsonParser);\n      if (!node.has(\"name\")) {\n        throw new IOException(\"Could not parse layer entry, missing required property 'name'\");\n      }\n      if (node.has(\"archive\")) {\n        return jsonParser.getCodec().treeToValue(node, ArchiveLayerSpec.class);\n      }\n      if (node.has(\"files\")) {\n        return jsonParser.getCodec().treeToValue(node, FileLayerSpec.class);\n      }\n      throw new IOException(\"Could not parse entry into ArchiveLayer or FileLayer\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/Layers.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport java.io.IOException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.PathMatcher;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.BiFunction;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/** Class to convert between different layer representations. */\nclass Layers {\n\n  private Layers() {}\n\n  /**\n   * Convert a layer spec to a list of layer objects.\n   *\n   * <p>Does not handle missing directories for files added via this method. We can either prefill\n   * directories here, or allow passing of the file entry information directly to the reproducible\n   * layer builder\n   *\n   * @param buildRoot the directory to resolve relative paths, usually the directory where the build\n   *     config file is located\n   * @param layersSpec a layersSpec containing configuration for all layers\n   * @return a {@link List} of {@link FileEntriesLayer} to use as part of a jib container build\n   * @throws IOException if traversing a directory fails\n   */\n  static List<FileEntriesLayer> toLayers(Path buildRoot, LayersSpec layersSpec) throws IOException {\n    List<FileEntriesLayer> layers = new ArrayList<>();\n\n    FilePropertiesStack filePropertiesStack = new FilePropertiesStack();\n    // base properties\n    layersSpec.getProperties().ifPresent(filePropertiesStack::push);\n\n    for (LayerSpec entry : layersSpec.getEntries()) {\n      // each loop is a new layer\n      if (entry instanceof FileLayerSpec) {\n        FileEntriesLayer.Builder layerBuiler = FileEntriesLayer.builder();\n\n        FileLayerSpec fileLayer = (FileLayerSpec) entry;\n        layerBuiler.setName(fileLayer.getName());\n        // layer properties\n        fileLayer.getProperties().ifPresent(filePropertiesStack::push);\n        for (CopySpec copySpec : ((FileLayerSpec) entry).getFiles()) {\n          // copy spec properties\n          copySpec.getProperties().ifPresent(filePropertiesStack::push);\n\n          // relativize all paths to the buildRoot location\n          Path rawSrc = copySpec.getSrc();\n          Path src = rawSrc.isAbsolute() ? rawSrc : buildRoot.resolve(rawSrc);\n          AbsoluteUnixPath dest = copySpec.getDest();\n\n          if (!Files.isDirectory(src) && !Files.isRegularFile(src)) {\n            throw new UnsupportedOperationException(\n                \"Cannot create FileLayers from non-file, non-directory: \" + src.toString());\n          }\n          if (Files.isRegularFile(src)) { // regular file\n            if (!copySpec.getExcludes().isEmpty() || !copySpec.getIncludes().isEmpty()) {\n              throw new UnsupportedOperationException(\n                  \"Cannot apply includes/excludes on single file copy directives.\");\n            }\n            layerBuiler.addEntry(\n                src,\n                copySpec.isDestEndsWithSlash() ? dest.resolve(src.getFileName()) : dest,\n                filePropertiesStack.getFilePermissions(),\n                filePropertiesStack.getModificationTime(),\n                filePropertiesStack.getOwnership());\n          } else if (Files.isDirectory(src)) { // directory\n            List<PathMatcher> excludes =\n                copySpec.getExcludes().stream()\n                    .map(Layers::toPathMatcher)\n                    .collect(Collectors.toList());\n            List<PathMatcher> includes =\n                copySpec.getIncludes().stream()\n                    .map(Layers::toPathMatcher)\n                    .collect(Collectors.toList());\n            try (Stream<Path> dirWalk = Files.walk(src)) {\n              List<Path> filtered =\n                  dirWalk\n                      // filter out against excludes\n                      .filter(path -> excludes.stream().noneMatch(exclude -> exclude.matches(path)))\n                      .filter(\n                          path -> {\n                            // if there are no includes directives, include everything\n                            if (includes.isEmpty()) {\n                              return true;\n                            }\n                            // if there are includes directives, only include those specified\n                            for (PathMatcher matcher : includes) {\n                              if (matcher.matches(path)) {\n                                return true;\n                              }\n                            }\n                            return false;\n                          })\n                      .collect(Collectors.toList());\n\n              BiFunction<Path, FilePermissions, FileEntry> newEntry =\n                  (file, permission) ->\n                      new FileEntry(\n                          file,\n                          dest.resolve(src.relativize(file)),\n                          permission,\n                          filePropertiesStack.getModificationTime(),\n                          filePropertiesStack.getOwnership());\n\n              Set<Path> addedDirectories = new HashSet<>();\n              for (Path path : filtered) {\n                if (!Files.isDirectory(path) && !Files.isRegularFile(path)) {\n                  throw new UnsupportedOperationException(\n                      \"Cannot create FileLayers from non-file, non-directory: \" + src.toString());\n                }\n\n                if (Files.isDirectory(path)) {\n                  addedDirectories.add(path);\n                  layerBuiler.addEntry(\n                      newEntry.apply(path, filePropertiesStack.getDirectoryPermissions()));\n                } else if (Files.isRegularFile(path)) {\n                  if (!path.startsWith(src)) {\n                    // if we end up in a situation where the file added is somehow outside of the\n                    // tree then we do not know how to properly handle it at the moment. It could\n                    // be from a link scenario that we do not understand.\n                    throw new IllegalStateException(\n                        src.toString() + \" is not a parent of \" + path.toString());\n                  }\n                  Path parent = Verify.verifyNotNull(path.getParent());\n                  while (true) {\n                    if (addedDirectories.contains(parent)) {\n                      break;\n                    }\n                    layerBuiler.addEntry(\n                        newEntry.apply(parent, filePropertiesStack.getDirectoryPermissions()));\n                    addedDirectories.add(parent);\n                    if (parent.equals(src)) {\n                      break;\n                    }\n                    parent = Verify.verifyNotNull(parent.getParent());\n                  }\n                  layerBuiler.addEntry(\n                      newEntry.apply(path, filePropertiesStack.getFilePermissions()));\n                }\n              }\n            }\n          }\n          copySpec.getProperties().ifPresent(ignored -> filePropertiesStack.pop());\n        }\n        fileLayer.getProperties().ifPresent(ignored -> filePropertiesStack.pop());\n        // TODO: add logging/handling for empty layers\n        layers.add(layerBuiler.build());\n      } else {\n        throw new UnsupportedOperationException(\"Only FileLayers are supported at this time.\");\n      }\n    }\n    layersSpec.getProperties().ifPresent(ignored -> filePropertiesStack.pop());\n    return layers;\n  }\n\n  @VisibleForTesting\n  static PathMatcher toPathMatcher(String glob) {\n    return FileSystems.getDefault()\n        .getPathMatcher(\n            \"glob:\" + ((glob.endsWith(\"/\") || glob.endsWith(\"\\\\\")) ? glob + \"**\" : glob));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/LayersSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * A yaml block for specifying layers.\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * properties: see }{@link FilePropertiesSpec}{@code\n * entries:\n *   - see }{@link LayerSpec}{@code\n *   - see }{@link LayerSpec}{@code\n * }</pre>\n */\npublic class LayersSpec {\n  private final List<LayerSpec> entries;\n  @Nullable private final FilePropertiesSpec properties;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param entries a list of {@link LayerSpec} defining the layers in this container\n   * @param properties a {@link FilePropertiesSpec} that applies to all layers in this buildfile\n   */\n  @JsonCreator\n  public LayersSpec(\n      @JsonProperty(value = \"entries\", required = true) List<LayerSpec> entries,\n      @JsonProperty(\"properties\") FilePropertiesSpec properties) {\n    Validator.checkNotNullAndNotEmpty(entries, \"entries\");\n    this.entries = entries;\n    this.properties = properties;\n  }\n\n  public Optional<FilePropertiesSpec> getProperties() {\n    return Optional.ofNullable(properties);\n  }\n\n  public List<LayerSpec> getEntries() {\n    return entries;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/PlatformSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n/**\n * A yaml block for specifying platforms.\n *\n * <p>Example use of this yaml snippet.\n *\n * <pre>{@code\n * architecture: amd64\n * os: linux\n * os.version: 1.0.0\n * os.features:\n *   - headless\n * variant: amd64v10\n * features:\n *   - sse4\n *   - aes\n * }</pre>\n */\n// TODO: reintroduce platform details when ready\n// TODO: revert https://github.com/GoogleContainerTools/jib/pull/2763\npublic class PlatformSpec {\n  private final String architecture;\n  private final String os;\n\n  /**\n   * Constructor for use by jackson to populate this object.\n   *\n   * @param architecture the target cpu architecture\n   * @param os the target operating system\n   */\n  @JsonCreator\n  public PlatformSpec(\n      @JsonProperty(value = \"architecture\", required = true) String architecture,\n      @JsonProperty(value = \"os\", required = true) String os) {\n    Validator.checkNotNullAndNotEmpty(architecture, \"architecture\");\n    Validator.checkNotNullAndNotEmpty(os, \"os\");\n    this.architecture = architecture;\n    this.os = os;\n  }\n\n  public String getArchitecture() {\n    return architecture;\n  }\n\n  public String getOs() {\n    return os;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/buildfile/Validator.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.google.common.base.Preconditions;\nimport java.util.Collection;\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\n/**\n * Utility helper class to detect errors in parsed yaml values. This class is mostly concerned with\n * error message formatting, checking is delegated to guava.\n */\npublic class Validator {\n\n  private Validator() {}\n\n  /**\n   * Checks if string is non null and non empty.\n   *\n   * @param value the string in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @throws NullPointerException if {@code value} is null\n   * @throws IllegalArgumentException if {@code value} is empty or only whitespace\n   */\n  public static void checkNotNullAndNotEmpty(@Nullable String value, String propertyName) {\n    Preconditions.checkNotNull(value, \"Property '\" + propertyName + \"' cannot be null\");\n    Preconditions.checkArgument(\n        !value.trim().isEmpty(), \"Property '\" + propertyName + \"' cannot be an empty string\");\n  }\n\n  /**\n   * Checks if string is null or non empty.\n   *\n   * @param value the string in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @throws IllegalArgumentException if {@code value} is empty or only whitespace\n   */\n  public static void checkNullOrNotEmpty(@Nullable String value, String propertyName) {\n    if (value == null) {\n      // pass\n      return;\n    }\n    Preconditions.checkArgument(\n        !value.trim().isEmpty(), \"Property '\" + propertyName + \"' cannot be an empty string\");\n  }\n\n  /**\n   * Checks if a collection is not null and not empty.\n   *\n   * @param value the string in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @throws NullPointerException if {@code value} is null\n   * @throws IllegalArgumentException if {@code value} is empty\n   */\n  public static void checkNotNullAndNotEmpty(@Nullable Collection<?> value, String propertyName) {\n    Preconditions.checkNotNull(value, \"Property '\" + propertyName + \"' cannot be null\");\n    Preconditions.checkArgument(\n        !value.isEmpty(), \"Property '\" + propertyName + \"' cannot be an empty collection\");\n  }\n\n  /**\n   * Check if a collection is either null, empty or contains only non-null, non-empty values.\n   *\n   * @param values the collection in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @throws IllegalArgumentException if {@code values} contains empty entries\n   * @throws NullPointerException if {@code values} contains null entries\n   */\n  public static void checkNullOrNonNullNonEmptyEntries(\n      @Nullable Collection<String> values, String propertyName) {\n    if (values == null) {\n      // pass\n      return;\n    }\n    for (String value : values) {\n      Preconditions.checkNotNull(\n          value, \"Property '\" + propertyName + \"' cannot contain null entries\");\n      Preconditions.checkArgument(\n          !value.trim().isEmpty(), \"Property '\" + propertyName + \"' cannot contain empty strings\");\n    }\n  }\n\n  /**\n   * Check if a map is either null, empty or contains only non-null, non-empty keys and values.\n   *\n   * @param values the collection in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @throws IllegalArgumentException if {@code values} contains empty keys or values\n   * @throws NullPointerException if {@code values} contains null keys or values\n   */\n  public static void checkNullOrNonNullNonEmptyEntries(\n      @Nullable Map<String, String> values, String propertyName) {\n    if (values == null) {\n      // pass\n      return;\n    }\n    for (Map.Entry<String, String> entry : values.entrySet()) {\n      Preconditions.checkNotNull(\n          entry.getKey(), \"Property '\" + propertyName + \"' cannot contain null keys\");\n      Preconditions.checkArgument(\n          !entry.getKey().trim().isEmpty(),\n          \"Property '\" + propertyName + \"' cannot contain empty string keys\");\n      Preconditions.checkNotNull(\n          entry.getValue(), \"Property '\" + propertyName + \"' cannot contain null values\");\n      Preconditions.checkArgument(\n          !entry.getValue().trim().isEmpty(),\n          \"Property '\" + propertyName + \"' cannot contain empty string values\");\n    }\n  }\n\n  /**\n   * Check if a collection is either null, empty or contains only non-null values.\n   *\n   * @param values the collection in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @throws NullPointerException if {@code values} contains null entries\n   */\n  public static void checkNullOrNonNullEntries(\n      @Nullable Collection<?> values, String propertyName) {\n    if (values == null) {\n      // pass\n      return;\n    }\n    for (Object value : values) {\n      Preconditions.checkNotNull(\n          value, \"Property '\" + propertyName + \"' cannot contain null entries\");\n    }\n  }\n\n  /**\n   * Checks if string is equal to the expected string.\n   *\n   * @param value the string in question\n   * @param propertyName the equivalent 'yaml' property name\n   * @param expectedValue the value we expect {@code value} to be\n   * @throws NullPointerException if {@code value} is null\n   * @throws IllegalArgumentException if {@code value} is not equal to {@code expectedValue}\n   */\n  public static void checkEquals(\n      @Nullable String value, String propertyName, String expectedValue) {\n    Preconditions.checkNotNull(value, \"Property '\" + propertyName + \"' cannot be null\");\n    Preconditions.checkArgument(\n        value.equals(expectedValue),\n        \"Property '\" + propertyName + \"' must be '\" + expectedValue + \"' but is '\" + value + \"'\");\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarFiles.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.cloud.tools.jib.cli.CommonCliOptions;\nimport com.google.cloud.tools.jib.cli.CommonContainerConfigCliOptions;\nimport com.google.cloud.tools.jib.cli.ContainerBuilders;\nimport com.google.cloud.tools.jib.cli.Jar;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\n\n/** Class to build a container representation from the contents of a jar file. */\npublic class JarFiles {\n\n  private JarFiles() {}\n\n  /**\n   * Generates a {@link JibContainerBuilder} from contents of a jar file.\n   *\n   * @param processor jar processor\n   * @param jarOptions jar cli options\n   * @param commonCliOptions common cli options\n   * @param commonContainerConfigCliOptions common command line options shared between jar and war\n   *     command\n   * @param logger console logger\n   * @return JibContainerBuilder\n   * @throws IOException if I/O error occurs when opening the jar file or if temporary directory\n   *     provided doesn't exist\n   * @throws InvalidImageReferenceException if the base image reference is invalid\n   */\n  public static JibContainerBuilder toJibContainerBuilder(\n      ArtifactProcessor processor,\n      Jar jarOptions,\n      CommonCliOptions commonCliOptions,\n      CommonContainerConfigCliOptions commonContainerConfigCliOptions,\n      ConsoleLogger logger)\n      throws IOException, InvalidImageReferenceException {\n    String imageReference =\n        commonContainerConfigCliOptions.getFrom().orElseGet(() -> getDefaultBaseImage(processor));\n    JibContainerBuilder containerBuilder =\n        ContainerBuilders.create(imageReference, Collections.emptySet(), commonCliOptions, logger);\n\n    List<FileEntriesLayer> layers = processor.createLayers();\n    List<String> customEntrypoint = commonContainerConfigCliOptions.getEntrypoint();\n    List<String> entrypoint =\n        customEntrypoint.isEmpty()\n            ? processor.computeEntrypoint(jarOptions.getJvmFlags())\n            : customEntrypoint;\n\n    containerBuilder\n        .setEntrypoint(entrypoint)\n        .setFileEntriesLayers(layers)\n        .setExposedPorts(commonContainerConfigCliOptions.getExposedPorts())\n        .setVolumes(commonContainerConfigCliOptions.getVolumes())\n        .setEnvironment(commonContainerConfigCliOptions.getEnvironment())\n        .setLabels(commonContainerConfigCliOptions.getLabels())\n        .setProgramArguments(commonContainerConfigCliOptions.getProgramArguments());\n    commonContainerConfigCliOptions.getUser().ifPresent(containerBuilder::setUser);\n    commonContainerConfigCliOptions.getFormat().ifPresent(containerBuilder::setFormat);\n    commonContainerConfigCliOptions.getCreationTime().ifPresent(containerBuilder::setCreationTime);\n\n    return containerBuilder;\n  }\n\n  private static String getDefaultBaseImage(ArtifactProcessor processor) {\n    if (processor.getJavaVersion() <= 8) {\n      return \"eclipse-temurin:8-jre\";\n    }\n    if (processor.getJavaVersion() <= 11) {\n      return \"eclipse-temurin:11-jre\";\n    }\n    if (processor.getJavaVersion() <= 17) {\n      return \"eclipse-temurin:17-jre\";\n    }\n    if (processor.getJavaVersion() <= 21) {\n      return \"eclipse-temurin:21-jre\";\n    }\n    return \"eclipse-temurin:25-jre\";\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarLayers.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactLayers;\nimport com.google.common.base.Splitter;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Predicate;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\nimport java.util.stream.Collectors;\n\npublic class JarLayers {\n\n  static final AbsoluteUnixPath APP_ROOT = AbsoluteUnixPath.get(\"/app\");\n  static final String JAR = \"jar\";\n\n  private JarLayers() {}\n\n  static List<FileEntriesLayer> getDependenciesLayers(Path jarPath, ProcessingMode mode)\n      throws IOException {\n    // Get dependencies from Class-Path in the jar's manifest and add a layer each for non-snapshot\n    // and snapshot dependencies. If Class-Path is not present in the JAR's manifest then skip\n    // adding the dependencies layers.\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String classPath =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);\n      if (classPath == null) {\n        return new ArrayList<>();\n      }\n      List<FileEntriesLayer> layers = new ArrayList<>();\n      Path jarParent = jarPath.getParent() == null ? Paths.get(\"\") : jarPath.getParent();\n      Predicate<String> isSnapshot = name -> name.contains(\"SNAPSHOT\");\n      List<String> allDependencies = Splitter.onPattern(\"\\\\s+\").splitToList(classPath.trim());\n      List<Path> nonSnapshots =\n          allDependencies.stream()\n              .filter(isSnapshot.negate())\n              .map(Paths::get)\n              .collect(Collectors.toList());\n      List<Path> snapshots =\n          allDependencies.stream().filter(isSnapshot).map(Paths::get).collect(Collectors.toList());\n      if (!nonSnapshots.isEmpty()) {\n        FileEntriesLayer.Builder nonSnapshotLayer =\n            FileEntriesLayer.builder().setName(ArtifactLayers.DEPENDENCIES);\n        nonSnapshots.forEach(\n            path ->\n                addDependency(\n                    nonSnapshotLayer,\n                    jarParent.resolve(path),\n                    mode.equals(ProcessingMode.packaged)\n                        ? APP_ROOT.resolve(path)\n                        : APP_ROOT\n                            .resolve(ArtifactLayers.DEPENDENCIES)\n                            .resolve(path.getFileName())));\n        layers.add(nonSnapshotLayer.build());\n      }\n      if (!snapshots.isEmpty()) {\n        FileEntriesLayer.Builder snapshotLayer =\n            FileEntriesLayer.builder().setName(ArtifactLayers.SNAPSHOT_DEPENDENCIES);\n        snapshots.forEach(\n            path ->\n                addDependency(\n                    snapshotLayer,\n                    jarParent.resolve(path),\n                    mode.equals(ProcessingMode.packaged)\n                        ? APP_ROOT.resolve(path)\n                        : APP_ROOT\n                            .resolve(ArtifactLayers.DEPENDENCIES)\n                            .resolve(path.getFileName())));\n        layers.add(snapshotLayer.build());\n      }\n      return layers;\n    }\n  }\n\n  private static void addDependency(\n      FileEntriesLayer.Builder layerbuilder, Path fullDepPath, AbsoluteUnixPath pathOnContainer) {\n    if (!Files.exists(fullDepPath)) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Dependency required by the JAR (as specified in `Class-Path` in the JAR manifest) doesn't exist: %s\",\n              fullDepPath));\n    }\n    layerbuilder.addEntry(fullDepPath, pathOnContainer);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/ProcessingMode.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\npublic enum ProcessingMode {\n  exploded,\n  packaged\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/SpringBootExplodedProcessor.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactLayers;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.cloud.tools.jib.plugins.common.ZipUtil;\nimport com.google.common.base.Predicates;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.MoreFiles;\nimport com.google.common.io.RecursiveDeleteOption;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Predicate;\nimport java.util.jar.JarFile;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.zip.ZipEntry;\n\npublic class SpringBootExplodedProcessor implements ArtifactProcessor {\n\n  private static final String BOOT_INF = \"BOOT-INF\";\n\n  private final Path jarPath;\n  private final Path targetExplodedJarRoot;\n  private final Integer jarJavaVersion;\n\n  /**\n   * Constructor for {@link SpringBootExplodedProcessor}.\n   *\n   * @param jarPath path to jar file\n   * @param targetExplodedJarRoot path to exploded-jar root\n   * @param jarJavaVersion jar java version\n   */\n  public SpringBootExplodedProcessor(\n      Path jarPath, Path targetExplodedJarRoot, Integer jarJavaVersion) {\n    this.jarPath = jarPath;\n    this.targetExplodedJarRoot = targetExplodedJarRoot;\n    this.jarJavaVersion = jarJavaVersion;\n  }\n\n  @Override\n  public List<FileEntriesLayer> createLayers() throws IOException {\n    // Clear the exploded-artifact root first\n    if (Files.exists(targetExplodedJarRoot)) {\n      MoreFiles.deleteRecursively(targetExplodedJarRoot, RecursiveDeleteOption.ALLOW_INSECURE);\n    }\n\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      ZipUtil.unzip(jarPath, targetExplodedJarRoot, true);\n      ZipEntry layerIndex = jarFile.getEntry(BOOT_INF + \"/layers.idx\");\n      if (layerIndex != null) {\n        return createLayersForLayeredSpringBootJar(targetExplodedJarRoot);\n      }\n\n      Predicate<Path> isFile = Files::isRegularFile;\n\n      // Non-snapshot layer\n      Predicate<Path> isInBootInfLib =\n          path -> path.startsWith(targetExplodedJarRoot.resolve(BOOT_INF).resolve(\"lib\"));\n      Predicate<Path> isSnapshot = path -> path.getFileName().toString().contains(\"SNAPSHOT\");\n      Predicate<Path> isInBootInfLibAndIsNotSnapshot = isInBootInfLib.and(isSnapshot.negate());\n      Predicate<Path> nonSnapshotPredicate = isFile.and(isInBootInfLibAndIsNotSnapshot);\n      FileEntriesLayer nonSnapshotLayer =\n          ArtifactLayers.getDirectoryContentsAsLayer(\n              ArtifactLayers.DEPENDENCIES,\n              targetExplodedJarRoot,\n              nonSnapshotPredicate,\n              JarLayers.APP_ROOT);\n\n      // Snapshot layer\n      Predicate<Path> isInBootInfLibAndIsSnapshot = isInBootInfLib.and(isSnapshot);\n      Predicate<Path> snapshotPredicate = isFile.and(isInBootInfLibAndIsSnapshot);\n      FileEntriesLayer snapshotLayer =\n          ArtifactLayers.getDirectoryContentsAsLayer(\n              ArtifactLayers.SNAPSHOT_DEPENDENCIES,\n              targetExplodedJarRoot,\n              snapshotPredicate,\n              JarLayers.APP_ROOT);\n\n      // Spring-boot-loader layer.\n      Predicate<Path> isLoader = path -> path.startsWith(targetExplodedJarRoot.resolve(\"org\"));\n      Predicate<Path> loaderPredicate = isFile.and(isLoader);\n      FileEntriesLayer loaderLayer =\n          ArtifactLayers.getDirectoryContentsAsLayer(\n              \"spring-boot-loader\", targetExplodedJarRoot, loaderPredicate, JarLayers.APP_ROOT);\n\n      // Classes layer.\n      Predicate<Path> isClass = path -> path.getFileName().toString().endsWith(\".class\");\n      Predicate<Path> isInBootInfClasses =\n          path -> path.startsWith(targetExplodedJarRoot.resolve(BOOT_INF).resolve(\"classes\"));\n      Predicate<Path> classesPredicate = isInBootInfClasses.and(isClass);\n      FileEntriesLayer classesLayer =\n          ArtifactLayers.getDirectoryContentsAsLayer(\n              ArtifactLayers.CLASSES, targetExplodedJarRoot, classesPredicate, JarLayers.APP_ROOT);\n\n      // Resources layer.\n      Predicate<Path> isInMetaInf =\n          path -> path.startsWith(targetExplodedJarRoot.resolve(\"META-INF\"));\n      Predicate<Path> isResource = isInMetaInf.or(isInBootInfClasses.and(isClass.negate()));\n      Predicate<Path> resourcesPredicate = isFile.and(isResource);\n      FileEntriesLayer resourcesLayer =\n          ArtifactLayers.getDirectoryContentsAsLayer(\n              ArtifactLayers.RESOURCES,\n              targetExplodedJarRoot,\n              resourcesPredicate,\n              JarLayers.APP_ROOT);\n\n      return Arrays.asList(\n          nonSnapshotLayer, loaderLayer, snapshotLayer, resourcesLayer, classesLayer);\n    }\n  }\n\n  @Override\n  public ImmutableList<String> computeEntrypoint(List<String> jvmFlags) {\n    ImmutableList.Builder<String> entrypoint = ImmutableList.builder();\n    entrypoint.add(\"java\");\n    entrypoint.addAll(jvmFlags);\n    entrypoint.add(\"-cp\");\n    entrypoint.add(JarLayers.APP_ROOT.toString());\n    entrypoint.add(\"org.springframework.boot.loader.JarLauncher\");\n    return entrypoint.build();\n  }\n\n  @Override\n  public Integer getJavaVersion() {\n    return jarJavaVersion;\n  }\n\n  /**\n   * Creates layers as specified by the layers.idx file (located in the BOOT-INF/ directory of the\n   * JAR).\n   *\n   * @param localExplodedJarRoot Path to exploded JAR content root\n   * @return list of {@link FileEntriesLayer}\n   * @throws IOException when an IO error occurs\n   */\n  private static List<FileEntriesLayer> createLayersForLayeredSpringBootJar(\n      Path localExplodedJarRoot) throws IOException {\n    Path layerIndexPath = localExplodedJarRoot.resolve(BOOT_INF).resolve(\"layers.idx\");\n    Pattern layerNamePattern = Pattern.compile(\"- \\\"(.*)\\\":\");\n    Pattern layerEntryPattern = Pattern.compile(\"  - \\\"(.*)\\\"\");\n    Map<String, List<String>> layersMap = new LinkedHashMap<>();\n    List<String> layerEntries = null;\n    for (String line : Files.readAllLines(layerIndexPath, StandardCharsets.UTF_8)) {\n      Matcher layerMatcher = layerNamePattern.matcher(line);\n      Matcher entryMatcher = layerEntryPattern.matcher(line);\n      if (layerMatcher.matches()) {\n        layerEntries = new ArrayList<>();\n        String layerName = layerMatcher.group(1);\n        layersMap.put(layerName, layerEntries);\n      } else if (entryMatcher.matches()) {\n        Verify.verifyNotNull(layerEntries).add(entryMatcher.group(1));\n      } else {\n        throw new IllegalStateException(\n            \"Unable to parse BOOT-INF/layers.idx file in the JAR. Please check the format of layers.idx.\");\n      }\n    }\n\n    // If the layers.idx file looks like this, for example:\n    // - \"dependencies\":\n    //   - \"BOOT-INF/lib/dependency1.jar\"\n    // - \"application\":\n    //   - \"BOOT-INF/classes/\"\n    //   - \"META-INF/\"\n    // The predicate for the \"dependencies\" layer will be true if `path` is equal to\n    // `BOOT-INF/lib/dependency1.jar` and the predicate for the \"spring-boot-loader\" layer will be\n    // true if `path` is in either 'BOOT-INF/classes/` or `META-INF/`.\n    List<FileEntriesLayer> layers = new ArrayList<>();\n    for (Map.Entry<String, List<String>> entry : layersMap.entrySet()) {\n      String layerName = entry.getKey();\n      List<String> contents = entry.getValue();\n      if (!contents.isEmpty()) {\n        Predicate<Path> belongsToThisLayer =\n            isInListedDirectoryOrIsSameFile(contents, localExplodedJarRoot);\n        layers.add(\n            ArtifactLayers.getDirectoryContentsAsLayer(\n                layerName, localExplodedJarRoot, belongsToThisLayer, JarLayers.APP_ROOT));\n      }\n    }\n    return layers;\n  }\n\n  private static Predicate<Path> isInListedDirectoryOrIsSameFile(\n      List<String> layerContents, Path localExplodedJarRoot) {\n    Predicate<Path> predicate = Predicates.alwaysFalse();\n    for (String pathName : layerContents) {\n      if (pathName.endsWith(\"/\")) {\n        predicate = predicate.or(path -> path.startsWith(localExplodedJarRoot.resolve(pathName)));\n      } else {\n        predicate = predicate.or(path -> path.equals(localExplodedJarRoot.resolve(pathName)));\n      }\n    }\n    return predicate.and(Files::isRegularFile);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/SpringBootPackagedProcessor.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.common.collect.ImmutableList;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class SpringBootPackagedProcessor implements ArtifactProcessor {\n\n  private final Path jarPath;\n  private final Integer jarJavaVersion;\n\n  /**\n   * Constructor for {@link SpringBootPackagedProcessor}.\n   *\n   * @param jarPath path to jar file\n   * @param jarJavaVersion jar java version\n   */\n  public SpringBootPackagedProcessor(Path jarPath, Integer jarJavaVersion) {\n    this.jarPath = jarPath;\n    this.jarJavaVersion = jarJavaVersion;\n  }\n\n  @Override\n  public List<FileEntriesLayer> createLayers() {\n    FileEntriesLayer jarLayer =\n        FileEntriesLayer.builder()\n            .setName(JarLayers.JAR)\n            .addEntry(jarPath, JarLayers.APP_ROOT.resolve(jarPath.getFileName()))\n            .build();\n    return Collections.singletonList(jarLayer);\n  }\n\n  @Override\n  public ImmutableList<String> computeEntrypoint(List<String> jvmFlags) {\n    ImmutableList.Builder<String> entrypoint = ImmutableList.builder();\n    entrypoint.add(\"java\");\n    entrypoint.addAll(jvmFlags);\n    entrypoint.add(\"-jar\");\n    entrypoint.add(JarLayers.APP_ROOT + \"/\" + jarPath.getFileName().toString());\n    return entrypoint.build();\n  }\n\n  @Override\n  public Integer getJavaVersion() {\n    return jarJavaVersion;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/StandardExplodedProcessor.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactLayers;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.cloud.tools.jib.plugins.common.ZipUtil;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.MoreFiles;\nimport com.google.common.io.RecursiveDeleteOption;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.function.Predicate;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\n\npublic class StandardExplodedProcessor implements ArtifactProcessor {\n\n  private final Path jarPath;\n  private final Path targetExplodedJarRoot;\n  private final Integer jarJavaVersion;\n\n  /**\n   * Constructor for {@link StandardExplodedProcessor}.\n   *\n   * @param jarPath path to jar file\n   * @param targetExplodedJarRoot path to exploded-jar root\n   * @param jarJavaVersion jar java version\n   */\n  public StandardExplodedProcessor(\n      Path jarPath, Path targetExplodedJarRoot, Integer jarJavaVersion) {\n    this.jarPath = jarPath;\n    this.targetExplodedJarRoot = targetExplodedJarRoot;\n    this.jarJavaVersion = jarJavaVersion;\n  }\n\n  @Override\n  public List<FileEntriesLayer> createLayers() throws IOException {\n    // Clear the exploded-artifact root first\n    if (Files.exists(targetExplodedJarRoot)) {\n      MoreFiles.deleteRecursively(targetExplodedJarRoot, RecursiveDeleteOption.ALLOW_INSECURE);\n    }\n\n    // Add dependencies layers.\n    List<FileEntriesLayer> layers =\n        JarLayers.getDependenciesLayers(jarPath, ProcessingMode.exploded);\n\n    // Determine class and resource files in the directory containing jar contents and create\n    // FileEntriesLayer for each type of layer (classes or resources).\n    ZipUtil.unzip(jarPath, targetExplodedJarRoot, true);\n    Predicate<Path> isClassFile = path -> path.getFileName().toString().endsWith(\".class\");\n    Predicate<Path> isResourceFile = isClassFile.negate().and(Files::isRegularFile);\n    FileEntriesLayer classesLayer =\n        ArtifactLayers.getDirectoryContentsAsLayer(\n            ArtifactLayers.CLASSES,\n            targetExplodedJarRoot,\n            isClassFile,\n            JarLayers.APP_ROOT.resolve(\"explodedJar\"));\n    FileEntriesLayer resourcesLayer =\n        ArtifactLayers.getDirectoryContentsAsLayer(\n            ArtifactLayers.RESOURCES,\n            targetExplodedJarRoot,\n            isResourceFile,\n            JarLayers.APP_ROOT.resolve(\"explodedJar\"));\n\n    if (!resourcesLayer.getEntries().isEmpty()) {\n      layers.add(resourcesLayer);\n    }\n    if (!classesLayer.getEntries().isEmpty()) {\n      layers.add(classesLayer);\n    }\n    return layers;\n  }\n\n  @Override\n  public ImmutableList<String> computeEntrypoint(List<String> jvmFlags) throws IOException {\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String mainClass =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);\n      if (mainClass == null) {\n        throw new IllegalArgumentException(\n            \"`Main-Class:` attribute for an application main class not defined in the input JAR's \"\n                + \"manifest (`META-INF/MANIFEST.MF` in the JAR).\");\n      }\n      String classpath =\n          JarLayers.APP_ROOT + \"/explodedJar:\" + JarLayers.APP_ROOT + \"/dependencies/*\";\n      ImmutableList.Builder<String> entrypoint = ImmutableList.builder();\n      entrypoint.add(\"java\");\n      entrypoint.addAll(jvmFlags);\n      entrypoint.add(\"-cp\");\n      entrypoint.add(classpath);\n      entrypoint.add(mainClass);\n      return entrypoint.build();\n    }\n  }\n\n  @Override\n  public Integer getJavaVersion() {\n    return jarJavaVersion;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/StandardPackagedProcessor.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarFile;\n\npublic class StandardPackagedProcessor implements ArtifactProcessor {\n\n  private final Path jarPath;\n  private final Integer jarJavaVersion;\n\n  /**\n   * Constructor for {@link StandardPackagedProcessor}.\n   *\n   * @param jarPath path to jar file\n   * @param jarJavaVersion jar java version\n   */\n  public StandardPackagedProcessor(Path jarPath, Integer jarJavaVersion) {\n    this.jarPath = jarPath;\n    this.jarJavaVersion = jarJavaVersion;\n  }\n\n  @Override\n  public List<FileEntriesLayer> createLayers() throws IOException {\n    // Add dependencies layers.\n    List<FileEntriesLayer> layers =\n        JarLayers.getDependenciesLayers(jarPath, ProcessingMode.packaged);\n\n    // Add layer for jar.\n    FileEntriesLayer jarLayer =\n        FileEntriesLayer.builder()\n            .setName(JarLayers.JAR)\n            .addEntry(jarPath, JarLayers.APP_ROOT.resolve(jarPath.getFileName()))\n            .build();\n    layers.add(jarLayer);\n\n    return layers;\n  }\n\n  @Override\n  public ImmutableList<String> computeEntrypoint(List<String> jvmFlags) throws IOException {\n    try (JarFile jarFile = new JarFile(jarPath.toFile())) {\n      String mainClass =\n          jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);\n      if (mainClass == null) {\n        throw new IllegalArgumentException(\n            \"`Main-Class:` attribute for an application main class not defined in the input JAR's \"\n                + \"manifest (`META-INF/MANIFEST.MF` in the JAR).\");\n      }\n      ImmutableList.Builder<String> entrypoint = ImmutableList.builder();\n      entrypoint.add(\"java\");\n      entrypoint.addAll(jvmFlags);\n      entrypoint.add(\"-jar\");\n      entrypoint.add(JarLayers.APP_ROOT + \"/\" + jarPath.getFileName().toString());\n      return entrypoint.build();\n    }\n  }\n\n  @Override\n  public Integer getJavaVersion() {\n    return jarJavaVersion;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/logging/CliLogger.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.logging;\n\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLoggerBuilder;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.PrintWriter;\n\n/** A simple CLI logger that logs to the command line based on the configured log level. */\npublic class CliLogger {\n\n  private CliLogger() {}\n\n  /**\n   * Create a new logger for the CLI.\n   *\n   * @param verbosity the configure verbosity\n   * @param httpTraceLevel the log level for http trace\n   * @param consoleOutput the configured consoleOutput format\n   * @param stdout the writer to store stdout\n   * @param stderr the writer to store stderr\n   * @param executor a {@link SingleThreadedExecutor} to ensure that all messages are logged in a\n   *     sequential, deterministic order\n   * @return a new ConsoleLogger instance\n   */\n  public static ConsoleLogger newLogger(\n      Verbosity verbosity,\n      HttpTraceLevel httpTraceLevel,\n      ConsoleOutput consoleOutput,\n      PrintWriter stdout,\n      PrintWriter stderr,\n      SingleThreadedExecutor executor) {\n    boolean enableRichProgress =\n        isRichConsole(consoleOutput, httpTraceLevel) && verbosity.atLeast(Verbosity.lifecycle);\n    ConsoleLoggerBuilder builder =\n        enableRichProgress\n            ? ConsoleLoggerBuilder.rich(executor, false)\n            : ConsoleLoggerBuilder.plain(executor);\n    if (verbosity.atLeast(Verbosity.error)) {\n      builder.error(message -> stderr.println(\"[ERROR] \" + message));\n    }\n    if (verbosity.atLeast(Verbosity.warn)) {\n      builder.warn(message -> stdout.println(\"[WARN] \" + message));\n    }\n    if (verbosity.atLeast(Verbosity.lifecycle)) {\n      builder.lifecycle(stdout::println);\n      // Rich progress reporting will be through ProgressEvent (note this is not LogEvent of\n      // Level.PROGRESS), so we ignore PROGRESS LogEvent.\n      if (!enableRichProgress) {\n        builder.progress(stdout::println);\n      }\n    }\n    if (verbosity.atLeast(Verbosity.info)) {\n      builder.info(stdout::println);\n    }\n    if (verbosity.atLeast(Verbosity.debug)) {\n      builder.debug(stdout::println);\n    }\n\n    return builder.build();\n  }\n\n  @VisibleForTesting\n  static boolean isRichConsole(ConsoleOutput consoleOutput, HttpTraceLevel httpTraceLevel) {\n    if (httpTraceLevel != HttpTraceLevel.off) {\n      return false;\n    }\n\n    switch (consoleOutput) {\n      case plain:\n        return false;\n      case auto:\n        // Enables progress footer when ANSI is supported (Windows or TERM not 'dumb').\n        return System.getProperty(\"os.name\").startsWith(\"windows\")\n            || (System.console() != null && !\"dumb\".equals(System.getenv(\"TERM\")));\n      case rich:\n      default:\n        return true;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/logging/ConsoleOutput.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.logging;\n\npublic enum ConsoleOutput {\n  auto,\n  rich,\n  plain\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/logging/HttpTraceLevel.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.logging;\n\nimport java.util.logging.Level;\n\npublic enum HttpTraceLevel {\n  off(\"OFF\"),\n  config(\"CONFIG\"),\n  all(\"ALL\");\n\n  private final String value;\n\n  private HttpTraceLevel(String value) {\n    this.value = value;\n  }\n\n  public Level toJulLevel() {\n    return Level.parse(value);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/logging/Verbosity.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.logging;\n\npublic enum Verbosity {\n  quiet(0),\n  error(1),\n  warn(2),\n  lifecycle(3),\n  info(4),\n  debug(5);\n\n  private final int value;\n\n  private Verbosity(int value) {\n    this.value = value;\n  }\n\n  public int value() {\n    return value;\n  }\n\n  public boolean atLeast(Verbosity target) {\n    return value >= target.value;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/war/StandardWarExplodedProcessor.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.war;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.cli.ArtifactLayers;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.cloud.tools.jib.plugins.common.ZipUtil;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.MoreFiles;\nimport com.google.common.io.RecursiveDeleteOption;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.function.Predicate;\n\npublic class StandardWarExplodedProcessor implements ArtifactProcessor {\n\n  private final Path warPath;\n  private final Path targetExplodedWarRoot;\n  private final AbsoluteUnixPath appRoot;\n\n  /**\n   * Constructor for {@link StandardWarExplodedProcessor}.\n   *\n   * @param warPath path to WAR file\n   * @param targetExplodedWarRoot path to exploded-war root\n   * @param appRoot the absolute path of the app on the container\n   */\n  public StandardWarExplodedProcessor(\n      Path warPath, Path targetExplodedWarRoot, AbsoluteUnixPath appRoot) {\n    this.warPath = warPath;\n    this.targetExplodedWarRoot = targetExplodedWarRoot;\n    this.appRoot = appRoot;\n  }\n\n  @Override\n  public List<FileEntriesLayer> createLayers() throws IOException {\n    // Clear the exploded-artifact root first\n    if (Files.exists(targetExplodedWarRoot)) {\n      MoreFiles.deleteRecursively(targetExplodedWarRoot, RecursiveDeleteOption.ALLOW_INSECURE);\n    }\n\n    ZipUtil.unzip(warPath, targetExplodedWarRoot, true);\n    Predicate<Path> isFile = Files::isRegularFile;\n    Predicate<Path> isInWebInfLib =\n        path -> path.startsWith(targetExplodedWarRoot.resolve(\"WEB-INF\").resolve(\"lib\"));\n    Predicate<Path> isSnapshot = path -> path.getFileName().toString().contains(\"SNAPSHOT\");\n\n    // Non-snapshot layer\n    Predicate<Path> isInWebInfLibAndIsNotSnapshot = isInWebInfLib.and(isSnapshot.negate());\n    FileEntriesLayer nonSnapshotLayer =\n        ArtifactLayers.getDirectoryContentsAsLayer(\n            ArtifactLayers.DEPENDENCIES,\n            targetExplodedWarRoot,\n            isFile.and(isInWebInfLibAndIsNotSnapshot),\n            appRoot);\n\n    // Snapshot layer\n    Predicate<Path> isInWebInfLibAndIsSnapshot = isInWebInfLib.and(isSnapshot);\n    FileEntriesLayer snapshotLayer =\n        ArtifactLayers.getDirectoryContentsAsLayer(\n            ArtifactLayers.SNAPSHOT_DEPENDENCIES,\n            targetExplodedWarRoot,\n            isFile.and(isInWebInfLibAndIsSnapshot),\n            appRoot);\n\n    // Classes layer.\n    Predicate<Path> isClass = path -> path.getFileName().toString().endsWith(\".class\");\n    Predicate<Path> isInWebInfClasses =\n        path -> path.startsWith(targetExplodedWarRoot.resolve(\"WEB-INF\").resolve(\"classes\"));\n    Predicate<Path> classesPredicate = isInWebInfClasses.and(isClass);\n    FileEntriesLayer classesLayer =\n        ArtifactLayers.getDirectoryContentsAsLayer(\n            ArtifactLayers.CLASSES, targetExplodedWarRoot, classesPredicate, appRoot);\n\n    // Resources layer.\n    Predicate<Path> resourcesPredicate = isInWebInfLib.or(isClass).negate();\n    FileEntriesLayer resourcesLayer =\n        ArtifactLayers.getDirectoryContentsAsLayer(\n            ArtifactLayers.RESOURCES,\n            targetExplodedWarRoot,\n            isFile.and(resourcesPredicate),\n            appRoot);\n\n    ArrayList<FileEntriesLayer> layers = new ArrayList<>();\n    if (!nonSnapshotLayer.getEntries().isEmpty()) {\n      layers.add(nonSnapshotLayer);\n    }\n    if (!snapshotLayer.getEntries().isEmpty()) {\n      layers.add(snapshotLayer);\n    }\n    if (!resourcesLayer.getEntries().isEmpty()) {\n      layers.add(resourcesLayer);\n    }\n    if (!classesLayer.getEntries().isEmpty()) {\n      layers.add(classesLayer);\n    }\n\n    return layers;\n  }\n\n  @Override\n  public ImmutableList<String> computeEntrypoint(List<String> jvmFlags) {\n    throw new UnsupportedOperationException(\"Computing the entrypoint is currently not supported.\");\n  }\n\n  @Override\n  public Integer getJavaVersion() {\n    throw new UnsupportedOperationException(\n        \"Getting the java version from a WAR file is currently not supported.\");\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/main/java/com/google/cloud/tools/jib/cli/war/WarFiles.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.war;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.cloud.tools.jib.cli.CommonCliOptions;\nimport com.google.cloud.tools.jib.cli.CommonContainerConfigCliOptions;\nimport com.google.cloud.tools.jib.cli.ContainerBuilders;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\npublic class WarFiles {\n\n  private WarFiles() {}\n\n  /**\n   * Generates a {@link JibContainerBuilder} from contents of a WAR file.\n   *\n   * @param processor artifact processor\n   * @param commonCliOptions common cli options\n   * @param commonContainerConfigCliOptions common cli options shared between jar and war command\n   * @param logger console logger\n   * @return JibContainerBuilder\n   * @throws IOException if I/O error occurs when opening the war file or if temporary directory\n   *     provided doesn't exist\n   * @throws InvalidImageReferenceException if the base image reference is invalid\n   */\n  public static JibContainerBuilder toJibContainerBuilder(\n      ArtifactProcessor processor,\n      CommonCliOptions commonCliOptions,\n      CommonContainerConfigCliOptions commonContainerConfigCliOptions,\n      ConsoleLogger logger)\n      throws IOException, InvalidImageReferenceException {\n    String baseImage = commonContainerConfigCliOptions.getFrom().orElse(\"jetty\");\n    JibContainerBuilder containerBuilder =\n        ContainerBuilders.create(baseImage, Collections.emptySet(), commonCliOptions, logger);\n    List<String> programArguments = commonContainerConfigCliOptions.getProgramArguments();\n    if (!commonContainerConfigCliOptions.getProgramArguments().isEmpty()) {\n      containerBuilder.setProgramArguments(programArguments);\n    }\n    containerBuilder\n        .setEntrypoint(computeEntrypoint(commonContainerConfigCliOptions))\n        .setFileEntriesLayers(processor.createLayers())\n        .setExposedPorts(commonContainerConfigCliOptions.getExposedPorts())\n        .setVolumes(commonContainerConfigCliOptions.getVolumes())\n        .setEnvironment(commonContainerConfigCliOptions.getEnvironment())\n        .setLabels(commonContainerConfigCliOptions.getLabels());\n    commonContainerConfigCliOptions.getUser().ifPresent(containerBuilder::setUser);\n    commonContainerConfigCliOptions.getFormat().ifPresent(containerBuilder::setFormat);\n    commonContainerConfigCliOptions.getCreationTime().ifPresent(containerBuilder::setCreationTime);\n\n    return containerBuilder;\n  }\n\n  @Nullable\n  private static List<String> computeEntrypoint(\n      CommonContainerConfigCliOptions commonContainerConfigCliOptions)\n      throws InvalidImageReferenceException {\n    List<String> entrypoint = commonContainerConfigCliOptions.getEntrypoint();\n    if (!entrypoint.isEmpty()) {\n      return entrypoint;\n    }\n    if (commonContainerConfigCliOptions.isJettyBaseimage()) {\n      // Since we are using Jetty 12 or later as the default, the deploy module needs to be\n      // specified. See\n      // https://eclipse.dev/jetty/documentation/jetty-12/operations-guide/index.html\n      return ImmutableList.of(\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\");\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/ArtifactProcessorsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessor;\nimport com.google.cloud.tools.jib.cli.ArtifactProcessors;\nimport com.google.cloud.tools.jib.cli.CacheDirectories;\nimport com.google.cloud.tools.jib.cli.CommonContainerConfigCliOptions;\nimport com.google.cloud.tools.jib.cli.Jar;\nimport com.google.cloud.tools.jib.cli.War;\nimport com.google.cloud.tools.jib.cli.jar.ProcessingMode;\nimport com.google.cloud.tools.jib.cli.jar.SpringBootExplodedProcessor;\nimport com.google.cloud.tools.jib.cli.jar.SpringBootPackagedProcessor;\nimport com.google.cloud.tools.jib.cli.jar.StandardExplodedProcessor;\nimport com.google.cloud.tools.jib.cli.jar.StandardPackagedProcessor;\nimport com.google.cloud.tools.jib.cli.war.StandardWarExplodedProcessor;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ArtifactProcessors}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ArtifactProcessorsTest {\n\n  private static final String SPRING_BOOT = \"jar/spring-boot/springboot_sample.jar\";\n  private static final String STANDARD = \"jar/standard/emptyStandardJar.jar\";\n  private static final String STANDARD_WITH_INVALID_CLASS = \"jar/standard/jarWithInvalidClass.jar\";\n  private static final String STANDARD_WITH_EMPTY_CLASS_FILE =\n      \"jar/standard/standardJarWithOnlyClasses.jar\";\n  private static final String JAVA_18_JAR = \"jar/java18.jar\";\n\n  @Mock private CacheDirectories mockCacheDirectories;\n  @Mock private Jar mockJarCommand;\n  @Mock private War mockWarCommand;\n  @Mock private CommonContainerConfigCliOptions mockCommonContainerConfigCliOptions;\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testFromJar_standardExploded() throws IOException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(STANDARD).toURI());\n    Path explodedJarRoot = temporaryFolder.getRoot().toPath();\n    when(mockCacheDirectories.getExplodedArtifactDirectory()).thenReturn(explodedJarRoot);\n    when(mockJarCommand.getMode()).thenReturn(ProcessingMode.exploded);\n\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromJar(\n            jarPath, mockCacheDirectories, mockJarCommand, mockCommonContainerConfigCliOptions);\n\n    verify(mockCacheDirectories).getExplodedArtifactDirectory();\n    assertThat(processor).isInstanceOf(StandardExplodedProcessor.class);\n  }\n\n  @Test\n  public void testFromJar_standardPackaged() throws IOException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(STANDARD).toURI());\n    when(mockJarCommand.getMode()).thenReturn(ProcessingMode.packaged);\n\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromJar(\n            jarPath, mockCacheDirectories, mockJarCommand, mockCommonContainerConfigCliOptions);\n\n    verifyNoInteractions(mockCacheDirectories);\n    assertThat(processor).isInstanceOf(StandardPackagedProcessor.class);\n  }\n\n  @Test\n  public void testFromJar_springBootPackaged() throws IOException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(SPRING_BOOT).toURI());\n    when(mockJarCommand.getMode()).thenReturn(ProcessingMode.packaged);\n\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromJar(\n            jarPath, mockCacheDirectories, mockJarCommand, mockCommonContainerConfigCliOptions);\n\n    verifyNoInteractions(mockCacheDirectories);\n    assertThat(processor).isInstanceOf(SpringBootPackagedProcessor.class);\n  }\n\n  @Test\n  public void testFromJar_springBootExploded() throws IOException, URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(SPRING_BOOT).toURI());\n    Path explodedJarRoot = temporaryFolder.getRoot().toPath();\n    when(mockCacheDirectories.getExplodedArtifactDirectory()).thenReturn(explodedJarRoot);\n    when(mockJarCommand.getMode()).thenReturn(ProcessingMode.exploded);\n\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromJar(\n            jarPath, mockCacheDirectories, mockJarCommand, mockCommonContainerConfigCliOptions);\n\n    verify(mockCacheDirectories).getExplodedArtifactDirectory();\n    assertThat(processor).isInstanceOf(SpringBootExplodedProcessor.class);\n  }\n\n  @Test\n  public void testFromJar_incompatibleDefaultBaseImage() throws URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(JAVA_18_JAR).toURI());\n\n    IllegalStateException exception =\n        assertThrows(\n            IllegalStateException.class,\n            () ->\n                ArtifactProcessors.fromJar(\n                    jarPath,\n                    mockCacheDirectories,\n                    mockJarCommand,\n                    mockCommonContainerConfigCliOptions));\n\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\"The input JAR (\" + jarPath + \") is compiled with Java 18\");\n  }\n\n  @Test\n  public void testFromJar_incompatibleDefaultBaseImage_baseImageSpecified()\n      throws URISyntaxException, IOException {\n    Path jarPath = Paths.get(Resources.getResource(JAVA_18_JAR).toURI());\n    when(mockJarCommand.getMode()).thenReturn(ProcessingMode.exploded);\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.of(\"base-image\"));\n\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromJar(\n            jarPath, mockCacheDirectories, mockJarCommand, mockCommonContainerConfigCliOptions);\n\n    verify(mockCacheDirectories).getExplodedArtifactDirectory();\n    assertThat(processor).isInstanceOf(StandardExplodedProcessor.class);\n  }\n\n  @Test\n  public void testDetermineJavaMajorVersion_versionNotFound()\n      throws URISyntaxException, IOException {\n    Path jarPath = Paths.get(Resources.getResource(STANDARD).toURI());\n    Integer version = ArtifactProcessors.determineJavaMajorVersion(jarPath);\n    assertThat(version).isEqualTo(0);\n  }\n\n  @Test\n  public void testDetermineJavaMajorVersion_invalidClassFile() throws URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(STANDARD_WITH_INVALID_CLASS).toURI());\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> ArtifactProcessors.determineJavaMajorVersion(jarPath));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"The class file (class1.class) is of an invalid format.\");\n  }\n\n  @Test\n  public void testDetermineJavaMajorVersion_emptyClassFile() throws URISyntaxException {\n    Path jarPath = Paths.get(Resources.getResource(STANDARD_WITH_EMPTY_CLASS_FILE).toURI());\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> ArtifactProcessors.determineJavaMajorVersion(jarPath));\n    assertThat(exception).hasMessageThat().startsWith(\"Reached end of class file (class1.class)\");\n  }\n\n  @Test\n  public void testFromWar_noJettyBaseImageAndNoAppRoot() {\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () ->\n                ArtifactProcessors.fromWar(\n                    Paths.get(\"my-app.war\"),\n                    mockCacheDirectories,\n                    mockWarCommand,\n                    mockCommonContainerConfigCliOptions));\n\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\"Please set the app root of the container with `--app-root`\");\n  }\n\n  @Test\n  public void testFromWar_noJettyBaseImageAndAppRootPresent_success()\n      throws InvalidImageReferenceException {\n    when(mockWarCommand.getAppRoot()).thenReturn(Optional.of(AbsoluteUnixPath.get(\"/app-root\")));\n    when(mockCacheDirectories.getExplodedArtifactDirectory())\n        .thenReturn(Paths.get(\"exploded-artifact\"));\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromWar(\n            Paths.get(\"my-app.war\"),\n            mockCacheDirectories,\n            mockWarCommand,\n            mockCommonContainerConfigCliOptions);\n\n    assertThat(processor).isInstanceOf(StandardWarExplodedProcessor.class);\n  }\n\n  @Test\n  public void testFromWar_jettyBaseImageSpecified_success() throws InvalidImageReferenceException {\n    when(mockCommonContainerConfigCliOptions.isJettyBaseimage()).thenReturn(true);\n\n    ArtifactProcessor processor =\n        ArtifactProcessors.fromWar(\n            Paths.get(\"my-app.war\"),\n            mockCacheDirectories,\n            mockWarCommand,\n            mockCommonContainerConfigCliOptions);\n    assertThat(processor).isInstanceOf(StandardWarExplodedProcessor.class);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/api/ContainerizerTestProxy.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport java.nio.file.Path;\nimport java.util.Set;\n\n/**\n * A simple test proxy to access the innards of Containerizer which are otherwise package private.\n */\npublic class ContainerizerTestProxy {\n\n  private final Containerizer containerizer;\n\n  public ContainerizerTestProxy(Containerizer containerizer) {\n    this.containerizer = containerizer;\n  }\n\n  public boolean getAllowInsecureRegistries() {\n    return containerizer.getAllowInsecureRegistries();\n  }\n\n  public boolean isOfflineMode() {\n    return containerizer.isOfflineMode();\n  }\n\n  public String getToolName() {\n    return containerizer.getToolName();\n  }\n\n  public String getToolVersion() {\n    return containerizer.getToolVersion();\n  }\n\n  public boolean getAlwaysCacheBaseImage() {\n    return containerizer.getAlwaysCacheBaseImage();\n  }\n\n  public String getDescription() {\n    return containerizer.getDescription();\n  }\n\n  public ImageConfiguration getImageConfiguration() {\n    return containerizer.getImageConfiguration();\n  }\n\n  public Path getBaseImageLayersCacheDirectory() {\n    return containerizer.getBaseImageLayersCacheDirectory();\n  }\n\n  public Path getApplicationsLayersCacheDirectory() throws CacheDirectoryCreationException {\n    return containerizer.getApplicationLayersCacheDirectory();\n  }\n\n  public Set<String> getAdditionalTags() {\n    return containerizer.getAdditionalTags();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport javax.annotation.Nullable;\nimport org.junit.Assert;\n\n/** Test helpers for making HTTP requests. */\npublic class HttpRequestTester {\n\n  /**\n   * Verifies the response body. Repeatedly tries {@code url} at the interval of .5 seconds for up\n   * to 20 seconds until getting OK HTTP response code.\n   */\n  public static void verifyBody(String expectedBody, URL url) throws InterruptedException {\n    Assert.assertEquals(expectedBody, getContent(url));\n  }\n\n  /** Fetches the host to use for the http request. */\n  public static String fetchDockerHostForHttpRequest() {\n    if (System.getenv(\"KOKORO_JOB_CLUSTER\") != null\n        && System.getenv(\"KOKORO_JOB_CLUSTER\").equals(\"MACOS_EXTERNAL\")) {\n      return System.getenv(\"DOCKER_IP\");\n    } else if (System.getenv(\"KOKORO_JOB_CLUSTER\") != null\n        && System.getenv(\"KOKORO_JOB_CLUSTER\").equals(\"GCP_UBUNTU_DOCKER\")) {\n      return System.getenv(\"DOCKER_IP_UBUNTU\");\n    } else {\n      return \"localhost\";\n    }\n  }\n\n  @Nullable\n  private static String getContent(URL url) throws InterruptedException {\n    for (int i = 0; i < 40; i++) {\n      Thread.sleep(500);\n      try {\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {\n          try (InputStream in = connection.getInputStream()) {\n            return Blobs.writeToString(Blobs.from(in));\n          }\n        }\n      } catch (IOException ignored) {\n        // ignored\n      }\n    }\n    return null;\n  }\n\n  private HttpRequestTester() {}\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/api/JibContainerBuilderTestHelper.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.configuration.BuildContext;\n\n/** Test helper to expose package-private members of {@link JibContainerBuilder}. */\npublic class JibContainerBuilderTestHelper {\n\n  public static BuildContext toBuildContext(\n      JibContainerBuilder jibContainerBuilder, Containerizer containerizer)\n      throws CacheDirectoryCreationException {\n    return jibContainerBuilder.toBuildContext(containerizer);\n  }\n\n  private JibContainerBuilderTestHelper() {}\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/BuildTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.cli.logging.HttpTraceLevel;\nimport com.google.cloud.tools.jib.cli.logging.Verbosity;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport java.nio.file.Paths;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport picocli.CommandLine;\nimport picocli.CommandLine.MissingParameterException;\n\n@RunWith(JUnitParamsRunner.class)\npublic class BuildTest {\n  @Test\n  public void testParse_missingRequiredParams() {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class, () -> CommandLine.populateCommand(new Build(), \"\"));\n    assertThat(mpe.getMessage()).isEqualTo(\"Missing required option: '--target=<target-image>'\");\n  }\n\n  @Test\n  public void testParse_defaults() {\n    Build buildCommand = CommandLine.populateCommand(new Build(), \"-t\", \"test-image-ref\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(buildCommand.buildFileUnprocessed).isNull();\n    assertThat(buildCommand.getBuildFile()).isEqualTo(Paths.get(\"./jib.yaml\"));\n    assertThat(buildCommand.contextRoot).isEqualTo(Paths.get(\".\"));\n    assertThat(commonCliOptions.getAdditionalTags()).isEmpty();\n    assertThat(buildCommand.getTemplateParameters()).isEmpty();\n    assertThat(commonCliOptions.getProjectCache()).isEmpty();\n    assertThat(commonCliOptions.getBaseImageCache()).isEmpty();\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isFalse();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isFalse();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.lifecycle);\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.off);\n    assertThat(commonCliOptions.isSerialize()).isFalse();\n    assertThat(commonCliOptions.getImageJsonPath()).isEmpty();\n  }\n\n  @Test\n  public void testParse_shortFormParams() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"-t=test-image-ref\",\n            \"-c=test-context\",\n            \"-b=test-build-file\",\n            \"-p=param1=value1\",\n            \"-p=param2=value2\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(buildCommand.buildFileUnprocessed).isEqualTo(Paths.get(\"test-build-file\"));\n    assertThat(buildCommand.getBuildFile()).isEqualTo(Paths.get(\"test-build-file\"));\n    assertThat(buildCommand.contextRoot).isEqualTo(Paths.get(\"test-context\"));\n    assertThat(commonCliOptions.getAdditionalTags()).isEmpty();\n    assertThat(buildCommand.getTemplateParameters())\n        .isEqualTo(ImmutableMap.of(\"param1\", \"value1\", \"param2\", \"value2\"));\n    assertThat(commonCliOptions.getProjectCache()).isEmpty();\n    assertThat(commonCliOptions.getBaseImageCache()).isEmpty();\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isFalse();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isFalse();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.lifecycle);\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.off);\n    assertThat(commonCliOptions.isSerialize()).isFalse();\n    assertThat(commonCliOptions.getImageJsonPath()).isEmpty();\n  }\n\n  @Test\n  public void testParse_longFormParams() {\n    // this test does not check credential helpers, scroll down for specialized credential helper\n    // tests\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--context=test-context\",\n            \"--build-file=test-build-file\",\n            \"--parameter=param1=value1\",\n            \"--parameter=param2=value2\",\n            \"--additional-tags=tag1,tag2,tag3\",\n            \"--allow-insecure-registries\",\n            \"--send-credentials-over-http\",\n            \"--project-cache=test-project-cache\",\n            \"--base-image-cache=test-base-image-cache\",\n            \"--verbosity=info\",\n            \"--stacktrace\",\n            \"--http-trace\",\n            \"--serialize\",\n            \"--image-metadata-out=path/to/json/jib-image.json\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(buildCommand.buildFileUnprocessed).isEqualTo(Paths.get(\"test-build-file\"));\n    assertThat(buildCommand.getBuildFile()).isEqualTo(Paths.get(\"test-build-file\"));\n    assertThat(buildCommand.contextRoot).isEqualTo(Paths.get(\"test-context\"));\n    assertThat(commonCliOptions.getAdditionalTags())\n        .isEqualTo(ImmutableList.of(\"tag1\", \"tag2\", \"tag3\"));\n    assertThat(buildCommand.getTemplateParameters())\n        .isEqualTo(ImmutableMap.of(\"param1\", \"value1\", \"param2\", \"value2\"));\n    assertThat(commonCliOptions.getProjectCache()).hasValue(Paths.get(\"test-project-cache\"));\n    assertThat(commonCliOptions.getBaseImageCache()).hasValue(Paths.get(\"test-base-image-cache\"));\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isTrue();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isTrue();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.info);\n    assertThat(commonCliOptions.isStacktrace()).isTrue();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.config);\n    assertThat(commonCliOptions.isSerialize()).isTrue();\n    assertThat(commonCliOptions.getImageJsonPath())\n        .hasValue(Paths.get(\"path/to/json/jib-image.json\"));\n  }\n\n  @Test\n  public void testParse_buildFileDefaultForContext() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(), \"--target\", \"test-image-ref\", \"--context\", \"test-context\");\n    assertThat(buildCommand.buildFileUnprocessed).isNull();\n    assertThat(buildCommand.getBuildFile()).isEqualTo(Paths.get(\"test-context/jib.yaml\"));\n    assertThat(buildCommand.contextRoot).isEqualTo(Paths.get(\"test-context\"));\n  }\n\n  @Test\n  public void testParse_credentialHelper() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(), \"--target=test-image-ref\", \"--credential-helper=test-cred-helper\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n    assertThat(commonCliOptions.getCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toCredentialHelper() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(), \"--target=test-image-ref\", \"--to-credential-helper=test-cred-helper\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_fromCredentialHelper() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(), \"--target=test-image-ref\", \"--from-credential-helper=test-cred-helper\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_usernamePassword() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--username=test-username\",\n            \"--password=test-password\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toUsernamePassword() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username\",\n            \"--to-password=test-password\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_fromUsernamePassword() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--from-username=test-username\",\n            \"--from-password=test-password\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n  }\n\n  @Test\n  public void testParse_toAndFromUsernamePassword() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username-1\",\n            \"--to-password=test-password-1\",\n            \"--from-username=test-username-2\",\n            \"--from-password=test-password-2\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username-1\", \"test-password-1\"));\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username-2\", \"test-password-2\"));\n  }\n\n  @Test\n  public void testParse_toAndFromCredentialHelper() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=to-test-helper\",\n            \"--from-credential-helper=from-test-helper\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"to-test-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"from-test-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toUsernamePasswordAndFromCredentialHelper() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username\",\n            \"--to-password=test-password\",\n            \"--from-credential-helper=test-cred-helper\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toCredentialHelperAndFromUsernamePassword() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=test-cred-helper\",\n            \"--from-username=test-username\",\n            \"--from-password=test-password\");\n    CommonCliOptions commonCliOptions = buildCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n  }\n\n  private Object usernamePasswordPairs() {\n    return new Object[][] {\n      {\"--username\", \"--password\"},\n      {\"--to-username\", \"--to-password\"},\n      {\"--from-username\", \"--from-password\"}\n    };\n  }\n\n  @Test\n  @Parameters(method = \"usernamePasswordPairs\")\n  public void testParse_usernameWithoutPassword(String usernameField, String passwordField) {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Build(), \"--target\", \"test-image-ref\", usernameField, \"test-username\"));\n    assertThat(mpe.getMessage()).isEqualTo(\"Error: Missing required argument(s): \" + passwordField);\n  }\n\n  @Test\n  @Parameters(method = \"usernamePasswordPairs\")\n  public void testParse_passwordWithoutUsername(String usernameField, String passwordField) {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Build(), \"--target\", \"test-image-ref\", passwordField, \"test-password\"));\n    assertThat(mpe.getMessage())\n        .isEqualTo(\"Error: Missing required argument(s): \" + usernameField + \"=<username>\");\n  }\n\n  public String[][] incompatibleCredentialOptions() {\n    return new String[][] {\n      {\"--credential-helper=x\", \"--to-credential-helper=x\"},\n      {\"--credential-helper=x\", \"--from-credential-helper=x\"},\n      {\"--credential-helper=x\", \"--username=x\", \"--password=x\"},\n      {\"--credential-helper=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--credential-helper=x\", \"--to-username=x\", \"--to-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--to-username=x\", \"--to-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--to-credential-helper=x\"},\n      {\"--username=x\", \"--password=x\", \"--from-credential-helper=x\"},\n      {\"--from-credential-helper=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--to-credential-helper=x\", \"--to-password=x\", \"--to-username=x\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"incompatibleCredentialOptions\")\n  public void testParse_incompatibleCredentialOptions(String[] authArgs) {\n    CommandLine.MutuallyExclusiveArgsException meae =\n        assertThrows(\n            CommandLine.MutuallyExclusiveArgsException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Build(), ArrayUtils.add(authArgs, \"--target=ignored\")));\n    assertThat(meae)\n        .hasMessageThat()\n        .containsMatch(\"^Error: (\\\\[)*(--(from-|to-)?credential-helper|\\\\[--(username|password))\");\n  }\n\n  @Test\n  public void testValidate_nameMissingFail() {\n    Build buildCommand = CommandLine.populateCommand(new Build(), \"--target=tar://sometar.tar\");\n    CommandLine.ParameterException pex =\n        assertThrows(CommandLine.ParameterException.class, buildCommand.commonCliOptions::validate);\n    assertThat(pex.getMessage())\n        .isEqualTo(\"Missing option: --name must be specified when using --target=tar://....\");\n  }\n\n  @Test\n  public void testValidate_pass() {\n    Build buildCommand =\n        CommandLine.populateCommand(\n            new Build(), \"--target=tar://sometar.tar\", \"--name=test.io/test/test\");\n    buildCommand.commonCliOptions.validate();\n    // pass\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/CacheDirectoriesTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport picocli.CommandLine;\n\npublic class CacheDirectoriesTest {\n\n  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testCacheDirectories_defaults() throws IOException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), \"-t\", \"ignored\");\n    Path buildContext = temporaryFolder.newFolder(\"some-context\").toPath();\n    CacheDirectories cacheDirectories = CacheDirectories.from(commonCliOptions, buildContext);\n\n    Path expectedProjectCache =\n        Paths.get(System.getProperty(\"java.io.tmpdir\"))\n            .resolve(\"jib-cli-cache\")\n            .resolve(\"projects\")\n            .resolve(CacheDirectories.getProjectCacheDirectoryFromProject(buildContext));\n    assertThat(cacheDirectories.getBaseImageCache()).isEmpty();\n    assertThat(cacheDirectories.getProjectCache()).isEqualTo(expectedProjectCache);\n    assertThat(cacheDirectories.getApplicationLayersCache())\n        .isEqualTo(expectedProjectCache.resolve(\"application-layers\"));\n    assertThat(cacheDirectories.getExplodedArtifactDirectory())\n        .isEqualTo(expectedProjectCache.resolve(\"exploded-artifact\"));\n  }\n\n  @Test\n  public void testCacheDirectories_configuredValuesIgnoresBuildContext() throws IOException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(\n            new CommonCliOptions(),\n            \"-t=ignored\",\n            \"--base-image-cache=test-base-image-cache\",\n            \"--project-cache=test-project-cache\");\n    Path ignoredContext = temporaryFolder.newFolder(\"ignored\").toPath();\n    CacheDirectories cacheDirectories = CacheDirectories.from(commonCliOptions, ignoredContext);\n\n    assertThat(cacheDirectories.getBaseImageCache()).hasValue(Paths.get(\"test-base-image-cache\"));\n    assertThat(cacheDirectories.getProjectCache()).isEqualTo(Paths.get(\"test-project-cache\"));\n    assertThat(cacheDirectories.getApplicationLayersCache())\n        .isEqualTo(Paths.get(\"test-project-cache\").resolve(\"application-layers\"));\n    assertThat(cacheDirectories.getExplodedArtifactDirectory())\n        .isEqualTo(Paths.get(\"test-project-cache\").resolve(\"exploded-artifact\"));\n  }\n\n  @Test\n  public void testCacheDirectories_failIfContextIsNotDirectory() throws IOException {\n    Path badContext = temporaryFolder.newFile().toPath();\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), \"-t\", \"ignored\");\n\n    IllegalArgumentException exception =\n        Assert.assertThrows(\n            IllegalArgumentException.class,\n            () -> CacheDirectories.from(commonCliOptions, badContext));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"contextRoot must be a directory, but \" + badContext.toString() + \" is not.\");\n  }\n\n  @Test\n  public void testGetProjectCacheDirectoryFromProject_sameFileDifferentPaths() throws IOException {\n    temporaryFolder.newFolder(\"ignored\");\n    Path path = temporaryFolder.getRoot().toPath();\n    Path indirectPath = temporaryFolder.getRoot().toPath().resolve(\"ignored\").resolve(\"..\");\n\n    assertThat(path).isNotEqualTo(indirectPath); // the general equality should not hold true\n    assertThat(Files.isSameFile(path, indirectPath)).isTrue(); // path equality holds\n    assertThat(CacheDirectories.getProjectCacheDirectoryFromProject(path))\n        .isEqualTo(\n            CacheDirectories.getProjectCacheDirectoryFromProject(\n                indirectPath)); // our hash should hold\n  }\n\n  @Test\n  public void testGetProjectCacheDirectoryFromProject_different() {\n    assertThat(CacheDirectories.getProjectCacheDirectoryFromProject(Paths.get(\"1\")))\n        .isNotEqualTo(CacheDirectories.getProjectCacheDirectoryFromProject(Paths.get(\"2\")));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/ContainerBuildersTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.Optional;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ContainerBuilders}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ContainerBuildersTest {\n\n  @Mock private CommonCliOptions mockCommonCliOptions;\n\n  @Mock private ConsoleLogger mockLogger;\n\n  @Test\n  public void testCreate_dockerBaseImage()\n      throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException {\n    JibContainerBuilder containerBuilder =\n        ContainerBuilders.create(\n            \"docker://docker-image-ref\", Collections.emptySet(), mockCommonCliOptions, mockLogger);\n    BuildContext buildContext =\n        JibContainerBuilderTestHelper.toBuildContext(\n            containerBuilder, Containerizer.to(RegistryImage.named(\"ignored\")));\n    ImageConfiguration imageConfiguration = buildContext.getBaseImageConfiguration();\n\n    assertThat(imageConfiguration.getImage().toString()).isEqualTo(\"docker-image-ref\");\n    assertThat(imageConfiguration.getDockerClient().isPresent()).isTrue();\n    assertThat(imageConfiguration.getTarPath().isPresent()).isFalse();\n  }\n\n  @Test\n  public void testCreate_registry()\n      throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException {\n    JibContainerBuilder containerBuilder =\n        ContainerBuilders.create(\n            \"registry://registry-image-ref\",\n            Collections.emptySet(),\n            mockCommonCliOptions,\n            mockLogger);\n    BuildContext buildContext =\n        JibContainerBuilderTestHelper.toBuildContext(\n            containerBuilder, Containerizer.to(RegistryImage.named(\"ignored\")));\n    ImageConfiguration imageConfiguration = buildContext.getBaseImageConfiguration();\n\n    assertThat(imageConfiguration.getImage().toString()).isEqualTo(\"registry-image-ref\");\n    assertThat(imageConfiguration.getDockerClient().isPresent()).isFalse();\n    assertThat(imageConfiguration.getTarPath().isPresent()).isFalse();\n  }\n\n  @Test\n  public void testCreate_tarBase()\n      throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException {\n    JibContainerBuilder containerBuilder =\n        ContainerBuilders.create(\n            \"tar:///path/to.tar\", Collections.emptySet(), mockCommonCliOptions, mockLogger);\n    BuildContext buildContext =\n        JibContainerBuilderTestHelper.toBuildContext(\n            containerBuilder, Containerizer.to(RegistryImage.named(\"ignored\")));\n    ImageConfiguration imageConfiguration = buildContext.getBaseImageConfiguration();\n\n    assertThat(imageConfiguration.getTarPath()).isEqualTo(Optional.of(Paths.get(\"/path/to.tar\")));\n    assertThat(imageConfiguration.getDockerClient().isPresent()).isFalse();\n  }\n\n  @Test\n  public void testCreate_platforms() throws IOException, InvalidImageReferenceException {\n    JibContainerBuilder containerBuilder =\n        ContainerBuilders.create(\n            \"registry://registry-image-ref\",\n            ImmutableSet.of(new Platform(\"arch1\", \"os1\"), new Platform(\"arch2\", \"os2\")),\n            mockCommonCliOptions,\n            mockLogger);\n\n    assertThat(containerBuilder.toContainerBuildPlan().getPlatforms())\n        .isEqualTo(ImmutableSet.of(new Platform(\"arch1\", \"os1\"), new Platform(\"arch2\", \"os2\")));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/ContainerizersTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.ContainerizerTestProxy;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.rules.TemporaryFolder;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\nimport picocli.CommandLine;\n\npublic class ContainerizersTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n  @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule();\n  // Containerizers will add system properties based on cli properties\n  @Rule\n  public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();\n\n  @Mock private ConsoleLogger consoleLogger;\n  @Mock private CacheDirectories cacheDirectories;\n\n  private static final Path baseImageCache = Paths.get(\"base-image-cache-for-test\");\n  private static final Path applicationCache = Paths.get(\"application-cache-for-test\");\n\n  @Before\n  public void initCaches() {\n    Mockito.when(cacheDirectories.getBaseImageCache()).thenReturn(Optional.of(baseImageCache));\n    Mockito.when(cacheDirectories.getApplicationLayersCache()).thenReturn(applicationCache);\n  }\n\n  @Test\n  public void testApplyConfiguration_defaults()\n      throws InvalidImageReferenceException, FileNotFoundException,\n          CacheDirectoryCreationException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), \"-t\", \"test-image-ref\");\n    ContainerizerTestProxy containerizer =\n        new ContainerizerTestProxy(\n            Containerizers.from(commonCliOptions, consoleLogger, cacheDirectories));\n\n    assertThat(Boolean.getBoolean(JibSystemProperties.SEND_CREDENTIALS_OVER_HTTP)).isFalse();\n    assertThat(Boolean.getBoolean(JibSystemProperties.SERIALIZE)).isFalse();\n    assertThat(containerizer.getToolName()).isEqualTo(VersionInfo.TOOL_NAME);\n    assertThat(containerizer.getToolVersion()).isEqualTo(VersionInfo.getVersionSimple());\n    assertThat(Boolean.getBoolean(\"sendCredentialsOverHttp\")).isFalse();\n    assertThat(containerizer.getAllowInsecureRegistries()).isFalse();\n    assertThat(containerizer.getBaseImageLayersCacheDirectory()).isEqualTo(baseImageCache);\n    assertThat(containerizer.getApplicationsLayersCacheDirectory()).isEqualTo(applicationCache);\n    assertThat(containerizer.getAdditionalTags()).isEqualTo(ImmutableSet.of());\n  }\n\n  @Test\n  public void testApplyConfiguration_withValues()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException,\n          FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(\n            new CommonCliOptions(),\n            \"-t=test-image-ref\",\n            \"--send-credentials-over-http\",\n            \"--allow-insecure-registries\",\n            \"--additional-tags=tag1,tag2\",\n            \"--serialize\");\n    ContainerizerTestProxy containerizer =\n        new ContainerizerTestProxy(\n            Containerizers.from(commonCliOptions, consoleLogger, cacheDirectories));\n\n    assertThat(Boolean.getBoolean(JibSystemProperties.SEND_CREDENTIALS_OVER_HTTP)).isTrue();\n    assertThat(Boolean.getBoolean(JibSystemProperties.SERIALIZE)).isTrue();\n    assertThat(containerizer.getAllowInsecureRegistries()).isTrue();\n    assertThat(containerizer.getBaseImageLayersCacheDirectory()).isEqualTo(baseImageCache);\n    assertThat(containerizer.getApplicationsLayersCacheDirectory()).isEqualTo(applicationCache);\n    assertThat(containerizer.getAdditionalTags()).isEqualTo(ImmutableSet.of(\"tag1\", \"tag2\"));\n  }\n\n  @Test\n  public void testFrom_dockerDaemonImage()\n      throws InvalidImageReferenceException, FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(\n            new CommonCliOptions(), \"-t\", \"docker://gcr.io/test/test-image-ref\");\n    ContainerizerTestProxy containerizer =\n        new ContainerizerTestProxy(\n            Containerizers.from(commonCliOptions, consoleLogger, cacheDirectories));\n\n    assertThat(containerizer.getDescription()).isEqualTo(\"Building image to Docker daemon\");\n    ImageConfiguration config = containerizer.getImageConfiguration();\n\n    assertThat(config.getCredentialRetrievers()).isEmpty();\n    assertThat(config.getDockerClient()).isEmpty();\n    assertThat(config.getImage().toString()).isEqualTo(\"gcr.io/test/test-image-ref\");\n    assertThat(config.getTarPath()).isEmpty();\n  }\n\n  @Test\n  public void testFrom_tarImage() throws InvalidImageReferenceException, IOException {\n    Path tarPath = temporaryFolder.getRoot().toPath().resolve(\"test-tar.tar\");\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(\n            new CommonCliOptions(),\n            \"-t=tar://\" + tarPath.toAbsolutePath(),\n            \"--name=gcr.io/test/test-image-ref\");\n    ContainerizerTestProxy containerizer =\n        new ContainerizerTestProxy(\n            Containerizers.from(commonCliOptions, consoleLogger, cacheDirectories));\n\n    assertThat(containerizer.getDescription()).isEqualTo(\"Building image tarball\");\n    ImageConfiguration config = containerizer.getImageConfiguration();\n\n    assertThat(config.getCredentialRetrievers()).isEmpty();\n    assertThat(config.getDockerClient()).isEmpty();\n    assertThat(config.getImage().toString()).isEqualTo(\"gcr.io/test/test-image-ref\");\n    assertThat(config.getTarPath()).isEmpty(); // weird, but the way jib currently works\n  }\n\n  @Test\n  public void testFrom_registryImage() throws InvalidImageReferenceException, IOException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(\n            new CommonCliOptions(), \"-t\", \"registry://gcr.io/test/test-image-ref\");\n    ContainerizerTestProxy containerizer =\n        new ContainerizerTestProxy(\n            Containerizers.from(commonCliOptions, consoleLogger, cacheDirectories));\n\n    // description from Containerizer.java\n    assertThat(containerizer.getDescription()).isEqualTo(\"Building and pushing image\");\n    ImageConfiguration config = containerizer.getImageConfiguration();\n\n    assertThat(config.getCredentialRetrievers()).isNotEmpty();\n    assertThat(config.getDockerClient()).isEmpty();\n    assertThat(config.getImage().toString()).isEqualTo(\"gcr.io/test/test-image-ref\");\n    assertThat(config.getTarPath()).isEmpty();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/CredentialsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.plugins.common.DefaultCredentialRetrievers;\nimport java.io.FileNotFoundException;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\nimport picocli.CommandLine;\n\n@RunWith(JUnitParamsRunner.class)\npublic class CredentialsTest {\n\n  private static final String[] DEFAULT_ARGS = {\"--target=ignored\"};\n  @Rule public final MockitoRule mockitoJUnit = MockitoJUnit.rule();\n  @Mock private DefaultCredentialRetrievers defaultCredentialRetrievers;\n\n  private String[][] paramsToNone() {\n    return new String[][] {\n      {\"--from-credential-helper=ignored\"}, {\"--from-username=ignored\", \"--from-password=ignored\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"paramsToNone\")\n  public void testGetToCredentialRetriever_none(String[] args) throws FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args));\n    Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers);\n    verify(defaultCredentialRetrievers).asList();\n    verifyNoMoreInteractions(defaultCredentialRetrievers);\n  }\n\n  private String[][] paramsFromNone() {\n    return new String[][] {\n      {\"--to-credential-helper=ignored\"}, {\"--to-username=ignored\", \"--to-password=ignored\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"paramsFromNone\")\n  public void testGetFromCredentialRetriever_none(String[] args) throws FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args));\n    Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers);\n    verify(defaultCredentialRetrievers).asList();\n    verifyNoMoreInteractions(defaultCredentialRetrievers);\n  }\n\n  private String[][] paramsToCredHelper() {\n    return new String[][] {\n      {\"--credential-helper=abc\"},\n      {\"--to-credential-helper=abc\"},\n      {\"--to-credential-helper=abc\", \"--from-credential-helper=ignored\"},\n      {\"--to-credential-helper=abc\", \"--from-username=ignored\", \"--from-password=ignored\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"paramsToCredHelper\")\n  public void testGetToCredentialRetriever_credHelper(String[] args) throws FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args));\n    Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers);\n    verify(defaultCredentialRetrievers).setCredentialHelper(\"abc\");\n    verify(defaultCredentialRetrievers).asList();\n    verifyNoMoreInteractions(defaultCredentialRetrievers);\n  }\n\n  private String[][] paramsFromCredHelper() {\n    return new String[][] {\n      {\"--credential-helper=abc\"},\n      {\"--from-credential-helper=abc\"},\n      {\"--from-credential-helper=abc\", \"--to-credential-helper=ignored\"},\n      {\"--from-credential-helper=abc\", \"--to-username=ignored\", \"--to-password=ignored\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"paramsFromCredHelper\")\n  public void testGetFromCredentialHelper(String[] args) throws FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args));\n    Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers);\n    verify(defaultCredentialRetrievers).setCredentialHelper(\"abc\");\n    verify(defaultCredentialRetrievers).asList();\n    verifyNoMoreInteractions(defaultCredentialRetrievers);\n  }\n\n  public Object paramsToUsernamePassword() {\n    return new Object[][] {\n      {\"--username/--password\", new String[] {\"--username=abc\", \"--password=xyz\"}},\n      {\"--to-username/--to-password\", new String[] {\"--to-username=abc\", \"--to-password=xyz\"}},\n      {\n        \"--to-username/--to-password\",\n        new String[] {\n          \"--to-username=abc\",\n          \"--to-password=xyz\",\n          \"--from-username=ignored\",\n          \"--from-password=ignored\"\n        }\n      },\n      {\n        \"--to-username/--to-password\",\n        new String[] {\"--to-username=abc\", \"--to-password=xyz\", \"--from-credential-helper=ignored\"}\n      }\n    };\n  }\n\n  @Test\n  @Parameters(method = \"paramsToUsernamePassword\")\n  public void testGetToUsernamePassword(String expectedSource, String[] args)\n      throws FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args));\n    Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers);\n    ArgumentCaptor<Credential> captor = ArgumentCaptor.forClass(Credential.class);\n    verify(defaultCredentialRetrievers)\n        .setKnownCredential(captor.capture(), ArgumentMatchers.eq(expectedSource));\n    assertThat(captor.getValue()).isEqualTo(Credential.from(\"abc\", \"xyz\"));\n    verify(defaultCredentialRetrievers).asList();\n    verifyNoMoreInteractions(defaultCredentialRetrievers);\n  }\n\n  public Object paramsFromUsernamePassword() {\n    return new Object[][] {\n      {\"--username/--password\", new String[] {\"--username=abc\", \"--password=xyz\"}},\n      {\n        \"--from-username/--from-password\",\n        new String[] {\"--from-username=abc\", \"--from-password=xyz\"}\n      },\n      {\n        \"--from-username/--from-password\",\n        new String[] {\n          \"--from-username=abc\",\n          \"--from-password=xyz\",\n          \"--to-username=ignored\",\n          \"--to-password=ignored\"\n        }\n      },\n      {\n        \"--from-username/--from-password\",\n        new String[] {\n          \"--from-username=abc\", \"--from-password=xyz\", \"--to-credential-helper=ignored\"\n        }\n      },\n    };\n  }\n\n  @Test\n  @Parameters(method = \"paramsFromUsernamePassword\")\n  public void testGetFromUsernamePassword(String expectedSource, String[] args)\n      throws FileNotFoundException {\n    CommonCliOptions commonCliOptions =\n        CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args));\n    Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers);\n    ArgumentCaptor<Credential> captor = ArgumentCaptor.forClass(Credential.class);\n    verify(defaultCredentialRetrievers)\n        .setKnownCredential(captor.capture(), ArgumentMatchers.eq(expectedSource));\n    assertThat(captor.getValue()).isEqualTo(Credential.from(\"abc\", \"xyz\"));\n    verify(defaultCredentialRetrievers).asList();\n    verifyNoMoreInteractions(defaultCredentialRetrievers);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/InstantsTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport java.time.Instant;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Instants}. */\npublic class InstantsTest {\n\n  @Test\n  public void testFromMillisOrIso8601_millis() {\n    Instant parsed = Instants.fromMillisOrIso8601(\"100\", \"ignored\");\n    Assert.assertEquals(Instant.ofEpochMilli(100), parsed);\n  }\n\n  @Test\n  public void testFromMillisOrIso8601_iso8601() {\n    Instant parsed = Instants.fromMillisOrIso8601(\"2020-06-08T14:54:36+00:00\", \"ignored\");\n    Assert.assertEquals(Instant.parse(\"2020-06-08T14:54:36Z\"), parsed);\n  }\n\n  @Test\n  public void testFromMillisOrIso8601_failed() {\n    try {\n      Instants.fromMillisOrIso8601(\"bad-time\", \"testFieldName\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\n          \"testFieldName must be a number of milliseconds since epoch or an ISO 8601 formatted date\",\n          iae.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JarTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Ports;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.cli.jar.ProcessingMode;\nimport com.google.cloud.tools.jib.cli.logging.HttpTraceLevel;\nimport com.google.cloud.tools.jib.cli.logging.Verbosity;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport picocli.CommandLine;\nimport picocli.CommandLine.MissingParameterException;\n\n@RunWith(JUnitParamsRunner.class)\npublic class JarTest {\n\n  @Test\n  public void testParse_missingRequiredParams_targetImage() {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () -> CommandLine.populateCommand(new Jar(), \"my-app.jar\"));\n    assertThat(mpe)\n        .hasMessageThat()\n        .isEqualTo(\"Missing required option: '--target=<target-image>'\");\n  }\n\n  @Test\n  public void testParse_missingRequiredParams_jarfile() {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () -> CommandLine.populateCommand(new Jar(), \"--target=test-image-ref\"));\n    assertThat(mpe).hasMessageThat().isEqualTo(\"Missing required parameter: '<jarFile>'\");\n  }\n\n  @Test\n  public void testParse_defaults() {\n    Jar jarCommand = CommandLine.populateCommand(new Jar(), \"-t\", \"test-image-ref\", \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n    CommonContainerConfigCliOptions commonContainerConfigCliOptions =\n        jarCommand.commonContainerConfigCliOptions;\n\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getAdditionalTags()).isEmpty();\n    assertThat(commonCliOptions.getProjectCache()).isEmpty();\n    assertThat(commonCliOptions.getBaseImageCache()).isEmpty();\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isFalse();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isFalse();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.lifecycle);\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.off);\n    assertThat(commonCliOptions.isSerialize()).isFalse();\n    assertThat(commonCliOptions.getImageJsonPath()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getFrom()).isEmpty();\n    assertThat(jarCommand.getJvmFlags()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getExposedPorts()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getVolumes()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getEnvironment()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getLabels()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getUser()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getFormat()).hasValue(ImageFormat.Docker);\n    assertThat(commonContainerConfigCliOptions.getProgramArguments()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getEntrypoint()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getCreationTime()).isEmpty();\n    assertThat(jarCommand.getMode()).isEqualTo(ProcessingMode.exploded);\n  }\n\n  @Test\n  public void testParse_shortFormParams() {\n    Jar jarCommand = CommandLine.populateCommand(new Jar(), \"-t=test-image-ref\", \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getAdditionalTags()).isEmpty();\n    assertThat(commonCliOptions.getProjectCache()).isEmpty();\n    assertThat(commonCliOptions.getBaseImageCache()).isEmpty();\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isFalse();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isFalse();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.lifecycle);\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.off);\n    assertThat(commonCliOptions.isSerialize()).isFalse();\n    assertThat(commonCliOptions.getImageJsonPath()).isEmpty();\n  }\n\n  @Test\n  public void testParse_longFormParams() {\n    // this test does not check credential helpers, scroll down for specialized credential helper\n    // tests\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--additional-tags=tag1,tag2,tag3\",\n            \"--allow-insecure-registries\",\n            \"--send-credentials-over-http\",\n            \"--project-cache=test-project-cache\",\n            \"--base-image-cache=test-base-image-cache\",\n            \"--verbosity=info\",\n            \"--stacktrace\",\n            \"--http-trace\",\n            \"--serialize\",\n            \"--image-metadata-out=path/to/json/jib-image.json\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getAdditionalTags())\n        .isEqualTo(ImmutableList.of(\"tag1\", \"tag2\", \"tag3\"));\n    assertThat(commonCliOptions.getProjectCache()).hasValue(Paths.get(\"test-project-cache\"));\n    assertThat(commonCliOptions.getBaseImageCache()).hasValue(Paths.get(\"test-base-image-cache\"));\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isTrue();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isTrue();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.info);\n    assertThat(commonCliOptions.isStacktrace()).isTrue();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.config);\n    assertThat(commonCliOptions.isSerialize()).isTrue();\n    assertThat(commonCliOptions.getImageJsonPath())\n        .hasValue(Paths.get(\"path/to/json/jib-image.json\"));\n  }\n\n  @Test\n  public void testParse_credentialHelper() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--credential-helper=test-cred-helper\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n    assertThat(commonCliOptions.getCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toCredentialHelper() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=test-cred-helper\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_fromCredentialHelper() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--from-credential-helper=test-cred-helper\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_usernamePassword() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--username=test-username\",\n            \"--password=test-password\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toUsernamePassword() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username\",\n            \"--to-password=test-password\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_fromUsernamePassword() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--from-username=test-username\",\n            \"--from-password=test-password\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n  }\n\n  @Test\n  public void testParse_toAndFromUsernamePassword() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username-1\",\n            \"--to-password=test-password-1\",\n            \"--from-username=test-username-2\",\n            \"--from-password=test-password-2\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username-1\", \"test-password-1\"));\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username-2\", \"test-password-2\"));\n  }\n\n  @Test\n  public void testParse_toAndFromCredentialHelper() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=to-test-helper\",\n            \"--from-credential-helper=from-test-helper\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"to-test-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"from-test-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toUsernamePasswordAndFromCredentialHelper() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username\",\n            \"--to-password=test-password\",\n            \"--from-credential-helper=test-cred-helper\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toCredentialHelperAndFromUsernamePassword() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=test-cred-helper\",\n            \"--from-username=test-username\",\n            \"--from-password=test-password\",\n            \"my-app.jar\");\n    CommonCliOptions commonCliOptions = jarCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n  }\n\n  private Object usernamePasswordPairs() {\n    return new Object[][] {\n      {\"--username\", \"--password\"},\n      {\"--to-username\", \"--to-password\"},\n      {\"--from-username\", \"--from-password\"}\n    };\n  }\n\n  @Test\n  @Parameters(method = \"usernamePasswordPairs\")\n  public void testParse_usernameWithoutPassword(String usernameField, String passwordField) {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Jar(),\n                    \"--target=test-image-ref\",\n                    usernameField,\n                    \"test-username\",\n                    \"my-app.jar\"));\n    assertThat(mpe)\n        .hasMessageThat()\n        .isEqualTo(\"Error: Missing required argument(s): \" + passwordField);\n  }\n\n  @Test\n  @Parameters(method = \"usernamePasswordPairs\")\n  public void testParse_passwordWithoutUsername(String usernameField, String passwordField) {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Jar(),\n                    \"--target=test-image-ref\",\n                    passwordField,\n                    \"test-password\",\n                    \"my-app.jar\"));\n    assertThat(mpe)\n        .hasMessageThat()\n        .isEqualTo(\"Error: Missing required argument(s): \" + usernameField + \"=<username>\");\n  }\n\n  public String[][] incompatibleCredentialOptions() {\n    return new String[][] {\n      {\"--credential-helper=x\", \"--to-credential-helper=x\"},\n      {\"--credential-helper=x\", \"--from-credential-helper=x\"},\n      {\"--credential-helper=x\", \"--username=x\", \"--password=x\"},\n      {\"--credential-helper=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--credential-helper=x\", \"--to-username=x\", \"--to-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--to-username=x\", \"--to-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--to-credential-helper=x\"},\n      {\"--username=x\", \"--password=x\", \"--from-credential-helper=x\"},\n      {\"--from-credential-helper=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--to-credential-helper=x\", \"--to-password=x\", \"--to-username=x\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"incompatibleCredentialOptions\")\n  public void testParse_incompatibleCredentialOptions(String[] authArgs) {\n    CommandLine.MutuallyExclusiveArgsException meae =\n        assertThrows(\n            CommandLine.MutuallyExclusiveArgsException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Jar(), ArrayUtils.addAll(authArgs, \"--target=ignored\", \"my-app.jar\")));\n    assertThat(meae)\n        .hasMessageThat()\n        .containsMatch(\"^Error: (\\\\[)*(--(from-|to-)?credential-helper|\\\\[--(username|password))\");\n  }\n\n  @Test\n  public void testParse_from() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--from=base-image-ref\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getFrom()).hasValue(\"base-image-ref\");\n  }\n\n  @Test\n  public void testParse_jvmFlags() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--jvm-flags=jvm-flag1,jvm-flag2\", \"my-app.jar\");\n    assertThat(jarCommand.getJvmFlags()).isEqualTo(ImmutableList.of(\"jvm-flag1\", \"jvm-flag2\"));\n  }\n\n  @Test\n  public void testParse_exposedPorts() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--expose=8080,3306\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getExposedPorts())\n        .isEqualTo(Ports.parse(ImmutableList.of(\"8080\", \"3306\")));\n  }\n\n  @Test\n  public void testParse_volumes() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--volumes=/volume1,/volume2\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getVolumes())\n        .isEqualTo(\n            ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")));\n  }\n\n  @Test\n  public void testParse_environment() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--environment-variables=ENV_VAR1=value1,ENV_VAR2=value2\",\n            \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getEnvironment())\n        .isEqualTo(ImmutableMap.of(\"ENV_VAR1\", \"value1\", \"ENV_VAR2\", \"value2\"));\n  }\n\n  @Test\n  public void testParse_labels() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--labels=label1=value2,label2=value2\",\n            \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getLabels())\n        .isEqualTo(ImmutableMap.of(\"label1\", \"value2\", \"label2\", \"value2\"));\n  }\n\n  @Test\n  public void testParse_user() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--user=customUser\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getUser()).hasValue(\"customUser\");\n  }\n\n  @Test\n  public void testParse_imageFormat() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--image-format=OCI\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getFormat()).hasValue(ImageFormat.OCI);\n  }\n\n  @Test\n  public void testParse_invalidImageFormat() {\n    CommandLine.ParameterException exception =\n        assertThrows(\n            CommandLine.ParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Jar(), \"--target=test-image-ref\", \"--image-format=unknown\", \"my-app.jar\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"Invalid value for option '--image-format': expected one of [Docker, OCI] (case-sensitive) but was 'unknown'\");\n  }\n\n  @Test\n  public void testParse_programArguments() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--program-args=arg1,arg2\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getProgramArguments())\n        .isEqualTo(ImmutableList.of(\"arg1\", \"arg2\"));\n  }\n\n  @Test\n  public void testParse_entrypoint() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--entrypoint=java -cp myClass\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getEntrypoint())\n        .isEqualTo(ImmutableList.of(\"java\", \"-cp\", \"myClass\"));\n  }\n\n  @Test\n  public void testParse_creationTime_milliseconds() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--creation-time=23\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getCreationTime())\n        .hasValue(Instant.ofEpochMilli(23));\n  }\n\n  @Test\n  public void testParse_creationTime_iso8601() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(),\n            \"--target=test-image-ref\",\n            \"--creation-time=2011-12-03T22:42:05Z\",\n            \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.getCreationTime())\n        .hasValue(Instant.parse(\"2011-12-03T22:42:05Z\"));\n  }\n\n  @Test\n  public void testParse_mode() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--mode=packaged\", \"my-app.jar\");\n    assertThat(jarCommand.getMode()).isEqualTo(ProcessingMode.packaged);\n  }\n\n  @Test\n  public void testParse_invalidMode() {\n    CommandLine.ParameterException exception =\n        assertThrows(\n            CommandLine.ParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new Jar(), \"--target=test-image-ref\", \"--mode=unknown\", \"my-app.jar\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"Invalid value for option '--mode': expected one of [exploded, packaged] (case-sensitive) but was 'unknown'\");\n  }\n\n  @Test\n  public void testValidate_nameMissingFail() {\n    Jar jarCommand =\n        CommandLine.populateCommand(new Jar(), \"--target=tar://sometar.tar\", \"my-app.jar\");\n    CommandLine.ParameterException pex =\n        assertThrows(CommandLine.ParameterException.class, jarCommand.commonCliOptions::validate);\n    assertThat(pex)\n        .hasMessageThat()\n        .isEqualTo(\"Missing option: --name must be specified when using --target=tar://....\");\n  }\n\n  @Test\n  public void testValidate_pass() {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=tar://sometar.tar\", \"--name=test.io/test/test\", \"my-app.jar\");\n    jarCommand.commonCliOptions.validate();\n    // pass\n  }\n\n  @Test\n  public void testIsJetty_noCustomBaseImage() throws InvalidImageReferenceException {\n    Jar jarCommand =\n        CommandLine.populateCommand(new Jar(), \"--target=test-image-ref\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.isJettyBaseimage()).isTrue();\n  }\n\n  @Test\n  public void testIsJetty_nonJetty() throws InvalidImageReferenceException {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--from=base-image\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.isJettyBaseimage()).isFalse();\n  }\n\n  @Test\n  public void testIsJetty_customJetty() throws InvalidImageReferenceException {\n    Jar jarCommand =\n        CommandLine.populateCommand(\n            new Jar(), \"--target=test-image-ref\", \"--from=jetty:tag\", \"my-app.jar\");\n    assertThat(jarCommand.commonContainerConfigCliOptions.isJettyBaseimage()).isTrue();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JibCliTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.mockito.ArgumentMatchers.contains;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.cli.logging.Verbosity;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.plugins.common.ImageMetadataOutput;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestException;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n@RunWith(MockitoJUnitRunner.class)\npublic class JibCliTest {\n\n  @Mock private GlobalConfig globalConfig;\n  @Mock private ConsoleLogger logger;\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock private JibContainer mockJibContainer;\n\n  @Test\n  public void testConfigureHttpLogging() {\n    Logger logger = JibCli.configureHttpLogging(Level.ALL);\n    assertThat(logger.getName()).isEqualTo(\"com.google.api.client.http.HttpTransport\");\n    assertThat(logger.getLevel()).isEqualTo(Level.ALL);\n\n    assertThat(logger.getHandlers()).hasLength(1);\n    Handler handler = logger.getHandlers()[0];\n    assertThat(handler).isInstanceOf(ConsoleHandler.class);\n    assertThat(handler.getLevel()).isEqualTo(Level.ALL);\n  }\n\n  @Test\n  public void testLogTerminatingException() {\n    JibCli.logTerminatingException(logger, new IOException(\"test error message\"), false);\n\n    verify(logger)\n        .log(LogEvent.Level.ERROR, \"\\u001B[31;1mjava.io.IOException: test error message\\u001B[0m\");\n    verifyNoMoreInteractions(logger);\n  }\n\n  @Test\n  public void testLogTerminatingException_stackTrace() {\n    JibCli.logTerminatingException(logger, new IOException(\"test error message\"), true);\n\n    String stackTraceLine =\n        \"at com.google.cloud.tools.jib.cli.JibCliTest.testLogTerminatingException_stackTrace\";\n    verify(logger).log(eq(LogEvent.Level.ERROR), contains(stackTraceLine));\n    verify(logger)\n        .log(LogEvent.Level.ERROR, \"\\u001B[31;1mjava.io.IOException: test error message\\u001B[0m\");\n    verifyNoMoreInteractions(logger);\n  }\n\n  @Test\n  public void testNewUpdateChecker_noUpdateCheck() throws ExecutionException, InterruptedException {\n    when(globalConfig.isDisableUpdateCheck()).thenReturn(true);\n    Future<Optional<String>> updateChecker =\n        JibCli.newUpdateChecker(globalConfig, Verbosity.info, ignored -> {});\n    assertThat(updateChecker.get()).isEmpty();\n  }\n\n  @Test\n  public void testFinishUpdateChecker_correctMessageLogged() {\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.of(\"2.0.0\"));\n    JibCli.finishUpdateChecker(logger, updateCheckFuture);\n    verify(logger)\n        .log(\n            eq(LogEvent.Level.LIFECYCLE),\n            contains(\n                \"A new version of Jib CLI (2.0.0) is available (currently using \"\n                    + VersionInfo.getVersionSimple()\n                    + \"). Download the latest Jib CLI version from \"\n                    + ProjectInfo.GITHUB_URL\n                    + \"/releases/tag/v2.0.0-cli\"));\n  }\n\n  @Test\n  public void testWriteImageJson()\n      throws InvalidImageReferenceException, IOException, DigestException {\n    String imageId = \"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\";\n    String digest = \"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc\";\n    when(mockJibContainer.getTargetImage())\n        .thenReturn(ImageReference.parse(\"eclipse-temurin:8-jre\"));\n    when(mockJibContainer.getImageId()).thenReturn(DescriptorDigest.fromDigest(imageId));\n    when(mockJibContainer.getDigest()).thenReturn(DescriptorDigest.fromDigest(digest));\n    when(mockJibContainer.getTags()).thenReturn(ImmutableSet.of(\"latest\", \"tag-2\"));\n\n    Path outputPath = temporaryFolder.getRoot().toPath().resolve(\"jib-image.json\");\n    JibCli.writeImageJson(Optional.of(outputPath), mockJibContainer);\n\n    String outputJson = new String(Files.readAllBytes(outputPath), StandardCharsets.UTF_8);\n    ImageMetadataOutput metadataOutput =\n        JsonTemplateMapper.readJson(outputJson, ImageMetadataOutput.class);\n    assertThat(metadataOutput.getImage()).isEqualTo(\"eclipse-temurin:8-jre\");\n    assertThat(metadataOutput.getImageId()).isEqualTo(imageId);\n    assertThat(metadataOutput.getImageDigest()).isEqualTo(digest);\n    assertThat(metadataOutput.getTags()).containsExactly(\"latest\", \"tag-2\");\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/WarTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Ports;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.cli.logging.HttpTraceLevel;\nimport com.google.cloud.tools.jib.cli.logging.Verbosity;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport picocli.CommandLine;\nimport picocli.CommandLine.MissingParameterException;\n\n@RunWith(JUnitParamsRunner.class)\npublic class WarTest {\n\n  @Test\n  public void testParse_missingRequiredParams_targetImage() {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () -> CommandLine.populateCommand(new War(), \"my-app.war\"));\n    assertThat(mpe)\n        .hasMessageThat()\n        .isEqualTo(\"Missing required option: '--target=<target-image>'\");\n  }\n\n  @Test\n  public void testParse_missingRequiredParams_warfile() {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () -> CommandLine.populateCommand(new War(), \"--target=test-image-ref\"));\n    assertThat(mpe).hasMessageThat().isEqualTo(\"Missing required parameter: '<warFile>'\");\n  }\n\n  @Test\n  public void testParse_defaults() {\n    War warCommand = CommandLine.populateCommand(new War(), \"-t\", \"test-image-ref\", \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n    CommonContainerConfigCliOptions commonContainerConfigCliOptions =\n        warCommand.commonContainerConfigCliOptions;\n\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getAdditionalTags()).isEmpty();\n    assertThat(commonCliOptions.getProjectCache()).isEmpty();\n    assertThat(commonCliOptions.getBaseImageCache()).isEmpty();\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isFalse();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isFalse();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.lifecycle);\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.off);\n    assertThat(commonCliOptions.isSerialize()).isFalse();\n    assertThat(commonCliOptions.getImageJsonPath()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getFrom()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getExposedPorts()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getVolumes()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getEnvironment()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getLabels()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getUser()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getFormat()).hasValue(ImageFormat.Docker);\n    assertThat(commonContainerConfigCliOptions.getProgramArguments()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getEntrypoint()).isEmpty();\n    assertThat(commonContainerConfigCliOptions.getCreationTime()).isEmpty();\n    assertThat(warCommand.getAppRoot()).isEmpty();\n  }\n\n  @Test\n  public void testParse_shortFormParams() {\n    War warCommand = CommandLine.populateCommand(new War(), \"-t=test-image-ref\", \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getAdditionalTags()).isEmpty();\n    assertThat(commonCliOptions.getProjectCache()).isEmpty();\n    assertThat(commonCliOptions.getBaseImageCache()).isEmpty();\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isFalse();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isFalse();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.lifecycle);\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.isStacktrace()).isFalse();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.off);\n    assertThat(commonCliOptions.isSerialize()).isFalse();\n    assertThat(commonCliOptions.getImageJsonPath()).isEmpty();\n  }\n\n  @Test\n  public void testParse_longFormParams() {\n    // this test does not check credential helpers, scroll down for specialized credential helper\n    // tests\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--additional-tags=tag1,tag2,tag3\",\n            \"--allow-insecure-registries\",\n            \"--send-credentials-over-http\",\n            \"--project-cache=test-project-cache\",\n            \"--base-image-cache=test-base-image-cache\",\n            \"--verbosity=info\",\n            \"--stacktrace\",\n            \"--http-trace\",\n            \"--serialize\",\n            \"--image-metadata-out=path/to/json/jib-image.json\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n    assertThat(commonCliOptions.getTargetImage()).isEqualTo(\"test-image-ref\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getAdditionalTags())\n        .isEqualTo(ImmutableList.of(\"tag1\", \"tag2\", \"tag3\"));\n    assertThat(commonCliOptions.getProjectCache()).hasValue(Paths.get(\"test-project-cache\"));\n    assertThat(commonCliOptions.getBaseImageCache()).hasValue(Paths.get(\"test-base-image-cache\"));\n    assertThat(commonCliOptions.isAllowInsecureRegistries()).isTrue();\n    assertThat(commonCliOptions.isSendCredentialsOverHttp()).isTrue();\n    assertThat(commonCliOptions.getVerbosity()).isEqualTo(Verbosity.info);\n    assertThat(commonCliOptions.isStacktrace()).isTrue();\n    assertThat(commonCliOptions.getHttpTrace()).isEqualTo(HttpTraceLevel.config);\n    assertThat(commonCliOptions.isSerialize()).isTrue();\n    assertThat(commonCliOptions.getImageJsonPath())\n        .hasValue(Paths.get(\"path/to/json/jib-image.json\"));\n  }\n\n  @Test\n  public void testParse_credentialHelper() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--credential-helper=test-cred-helper\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n    assertThat(commonCliOptions.getCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toCredentialHelper() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=test-cred-helper\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_fromCredentialHelper() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--from-credential-helper=test-cred-helper\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_usernamePassword() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--username=test-username\",\n            \"--password=test-password\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toUsernamePassword() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username\",\n            \"--to-password=test-password\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_fromUsernamePassword() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--from-username=test-username\",\n            \"--from-password=test-password\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n  }\n\n  @Test\n  public void testParse_toAndFromUsernamePassword() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username-1\",\n            \"--to-password=test-password-1\",\n            \"--from-username=test-username-2\",\n            \"--from-password=test-password-2\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username-1\", \"test-password-1\"));\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username-2\", \"test-password-2\"));\n  }\n\n  @Test\n  public void testParse_toAndFromCredentialHelper() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=to-test-helper\",\n            \"--from-credential-helper=from-test-helper\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"to-test-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"from-test-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toUsernamePasswordAndFromCredentialHelper() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--to-username=test-username\",\n            \"--to-password=test-password\",\n            \"--from-credential-helper=test-cred-helper\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getFromCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n    assertThat(commonCliOptions.getFromUsernamePassword()).isEmpty();\n  }\n\n  @Test\n  public void testParse_toCredentialHelperAndFromUsernamePassword() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--to-credential-helper=test-cred-helper\",\n            \"--from-username=test-username\",\n            \"--from-password=test-password\",\n            \"my-app.war\");\n    CommonCliOptions commonCliOptions = warCommand.commonCliOptions;\n\n    assertThat(commonCliOptions.getCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getToCredentialHelper()).hasValue(\"test-cred-helper\");\n    assertThat(commonCliOptions.getFromCredentialHelper()).isEmpty();\n    assertThat(commonCliOptions.getUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getToUsernamePassword()).isEmpty();\n    assertThat(commonCliOptions.getFromUsernamePassword())\n        .hasValue(Credential.from(\"test-username\", \"test-password\"));\n  }\n\n  private Object usernamePasswordPairs() {\n    return new Object[][] {\n      {\"--username\", \"--password\"},\n      {\"--to-username\", \"--to-password\"},\n      {\"--from-username\", \"--from-password\"}\n    };\n  }\n\n  @Test\n  @Parameters(method = \"usernamePasswordPairs\")\n  public void testParse_usernameWithoutPassword(String usernameField, String passwordField) {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new War(),\n                    \"--target=test-image-ref\",\n                    usernameField,\n                    \"test-username\",\n                    \"my-app.war\"));\n    assertThat(mpe)\n        .hasMessageThat()\n        .isEqualTo(\"Error: Missing required argument(s): \" + passwordField);\n  }\n\n  @Test\n  @Parameters(method = \"usernamePasswordPairs\")\n  public void testParse_passwordWithoutUsername(String usernameField, String passwordField) {\n    MissingParameterException mpe =\n        assertThrows(\n            MissingParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new War(),\n                    \"--target=test-image-ref\",\n                    passwordField,\n                    \"test-password\",\n                    \"my-app.war\"));\n    assertThat(mpe)\n        .hasMessageThat()\n        .isEqualTo(\"Error: Missing required argument(s): \" + usernameField + \"=<username>\");\n  }\n\n  public String[][] incompatibleCredentialOptions() {\n    return new String[][] {\n      {\"--credential-helper=x\", \"--to-credential-helper=x\"},\n      {\"--credential-helper=x\", \"--from-credential-helper=x\"},\n      {\"--credential-helper=x\", \"--username=x\", \"--password=x\"},\n      {\"--credential-helper=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--credential-helper=x\", \"--to-username=x\", \"--to-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--to-username=x\", \"--to-password=x\"},\n      {\"--username=x\", \"--password=x\", \"--to-credential-helper=x\"},\n      {\"--username=x\", \"--password=x\", \"--from-credential-helper=x\"},\n      {\"--from-credential-helper=x\", \"--from-username=x\", \"--from-password=x\"},\n      {\"--to-credential-helper=x\", \"--to-password=x\", \"--to-username=x\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"incompatibleCredentialOptions\")\n  public void testParse_incompatibleCredentialOptions(String[] authArgs) {\n    CommandLine.MutuallyExclusiveArgsException meae =\n        assertThrows(\n            CommandLine.MutuallyExclusiveArgsException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new War(), ArrayUtils.addAll(authArgs, \"--target=ignored\", \"my-app.war\")));\n    assertThat(meae)\n        .hasMessageThat()\n        .containsMatch(\"^Error: (\\\\[)*(--(from-|to-)?credential-helper|\\\\[--(username|password))\");\n  }\n\n  @Test\n  public void testParse_from() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--from=base-image-ref\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getFrom()).hasValue(\"base-image-ref\");\n  }\n\n  @Test\n  public void testParse_appRoot() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--app-root=/path/to/app\", \"my-app.war\");\n    assertThat(warCommand.getAppRoot()).hasValue(AbsoluteUnixPath.get(\"/path/to/app\"));\n  }\n\n  @Test\n  public void testParse_exposedPorts() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--expose=8080,3306\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getExposedPorts())\n        .isEqualTo(Ports.parse(ImmutableList.of(\"8080\", \"3306\")));\n  }\n\n  @Test\n  public void testParse_volumes() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--volumes=/volume1,/volume2\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getVolumes())\n        .isEqualTo(\n            ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")));\n  }\n\n  @Test\n  public void testParse_environment() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--environment-variables=ENV_VAR1=value1,ENV_VAR2=value2\",\n            \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getEnvironment())\n        .isEqualTo(ImmutableMap.of(\"ENV_VAR1\", \"value1\", \"ENV_VAR2\", \"value2\"));\n  }\n\n  @Test\n  public void testParse_labels() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--labels=label1=value2,label2=value2\",\n            \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getLabels())\n        .isEqualTo(ImmutableMap.of(\"label1\", \"value2\", \"label2\", \"value2\"));\n  }\n\n  @Test\n  public void testParse_user() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--user=customUser\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getUser()).hasValue(\"customUser\");\n  }\n\n  @Test\n  public void testParse_imageFormat() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--image-format=OCI\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getFormat()).hasValue(ImageFormat.OCI);\n  }\n\n  @Test\n  public void testParse_invalidImageFormat() {\n    CommandLine.ParameterException exception =\n        assertThrows(\n            CommandLine.ParameterException.class,\n            () ->\n                CommandLine.populateCommand(\n                    new War(), \"--target=test-image-ref\", \"--image-format=unknown\", \"my-app.war\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"Invalid value for option '--image-format': expected one of [Docker, OCI] (case-sensitive) but was 'unknown'\");\n  }\n\n  @Test\n  public void testParse_programArguments() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--program-args=arg1,arg2\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getProgramArguments())\n        .isEqualTo(ImmutableList.of(\"arg1\", \"arg2\"));\n  }\n\n  @Test\n  public void testParse_entrypoint() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--entrypoint=java -cp myClass\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getEntrypoint())\n        .isEqualTo(ImmutableList.of(\"java\", \"-cp\", \"myClass\"));\n  }\n\n  @Test\n  public void testParse_creationTime_milliseconds() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--creation-time=23\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getCreationTime())\n        .hasValue(Instant.ofEpochMilli(23));\n  }\n\n  @Test\n  public void testParse_creationTime_iso8601() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(),\n            \"--target=test-image-ref\",\n            \"--creation-time=2011-12-03T22:42:05Z\",\n            \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.getCreationTime())\n        .hasValue(Instant.parse(\"2011-12-03T22:42:05Z\"));\n  }\n\n  @Test\n  public void testValidate_nameMissingFail() {\n    War warCommand =\n        CommandLine.populateCommand(new War(), \"--target=tar://sometar.tar\", \"my-app.war\");\n    CommandLine.ParameterException pex =\n        assertThrows(CommandLine.ParameterException.class, warCommand.commonCliOptions::validate);\n    assertThat(pex)\n        .hasMessageThat()\n        .isEqualTo(\"Missing option: --name must be specified when using --target=tar://....\");\n  }\n\n  @Test\n  public void testValidate_pass() {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=tar://sometar.tar\", \"--name=test.io/test/test\", \"my-app.war\");\n    warCommand.commonCliOptions.validate();\n    // pass\n  }\n\n  @Test\n  public void testIsJetty_noCustomBaseImage() throws InvalidImageReferenceException {\n    War warCommand =\n        CommandLine.populateCommand(new War(), \"--target=test-image-ref\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.isJettyBaseimage()).isTrue();\n  }\n\n  @Test\n  public void testIsJetty_nonJetty() throws InvalidImageReferenceException {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--from=base-image\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.isJettyBaseimage()).isFalse();\n  }\n\n  @Test\n  public void testIsJetty_customJetty() throws InvalidImageReferenceException {\n    War warCommand =\n        CommandLine.populateCommand(\n            new War(), \"--target=test-image-ref\", \"--from=jetty:tag\", \"my-app.war\");\n    assertThat(warCommand.commonContainerConfigCliOptions.isJettyBaseimage()).isTrue();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/ArchiveLayerSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport java.nio.file.Paths;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ArchiveLayerSpec}. */\npublic class ArchiveLayerSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testArchiveLayerSpec_full() throws JsonProcessingException {\n    String data =\n        \"name: layer name\\n\" + \"archive: out/archive.tgz\\n\" + \"mediaType: test.media.type\";\n\n    ArchiveLayerSpec parsed = mapper.readValue(data, ArchiveLayerSpec.class);\n    Assert.assertEquals(\"layer name\", parsed.getName());\n    Assert.assertEquals(Paths.get(\"out/archive.tgz\"), parsed.getArchive());\n    Assert.assertEquals(\"test.media.type\", parsed.getMediaType().get());\n  }\n\n  @Test\n  public void testArchiveLayerSpec_nameRequired() {\n    String data = \"archive: out/archive\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'name'\"));\n    }\n  }\n\n  @Test\n  public void testArchiveLayerSpec_nameNonNull() {\n    String data = \"name: null\\n\" + \"archive: out/archive\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'name' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testArchiveLayerSpec_nameNonEmpty() {\n    String data = \"name: ''\\n\" + \"archive: out/archive\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'name' cannot be an empty string\"));\n    }\n  }\n\n  // With {@link LayerSpec.Deserializer#deserialize} this test seems pointless, but it still helps\n  // define the behavior of this class.\n  @Test\n  public void testArchiveLayerSpec_archiveRequired() {\n    String data = \"name: layer name\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'archive'\"));\n    }\n  }\n\n  @Test\n  public void testArchiveLayerSpec_archiveNonNull() {\n    String data = \"name: layer name\\n\" + \"archive: null\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'archive' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testArchiveLayerSpec_archiveNonEmpty() {\n    String data = \"name: layer name\\n\" + \"archive: ''\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'archive' cannot be an empty string\"));\n    }\n  }\n\n  @Test\n  public void testArchiveLayerSpec_mediaTypeNonEmpty() {\n    String data = \"name: layer name\\n\" + \"archive: out/archive.tgz\\n\" + \"mediaType: ' '\";\n\n    try {\n      mapper.readValue(data, ArchiveLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'mediaType' cannot be an empty string\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BaseImageSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.common.collect.ImmutableList;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link BaseImageSpec}. */\npublic class BaseImageSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testBaseImageSpec_full() throws JsonProcessingException {\n    String data =\n        \"image: gcr.io/example/jib\\n\"\n            + \"platforms:\\n\" // trivial platform spec\n            + \"  - architecture: amd64\\n\"\n            + \"    os: linux\\n\";\n\n    BaseImageSpec baseImageSpec = mapper.readValue(data, BaseImageSpec.class);\n    Assert.assertEquals(\"gcr.io/example/jib\", baseImageSpec.getImage());\n    Assert.assertEquals(\"amd64\", baseImageSpec.getPlatforms().get(0).getArchitecture());\n    Assert.assertEquals(\"linux\", baseImageSpec.getPlatforms().get(0).getOs());\n  }\n\n  @Test\n  public void testBaseImageSpec_imageRequired() {\n    String data =\n        \"platforms:\\n\" // trivial platform spec\n            + \"  - architecture: amd64\\n\"\n            + \"    os: linux\\n\";\n    try {\n      mapper.readValue(data, BaseImageSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'image'\"));\n    }\n  }\n\n  @Test\n  public void testBaseImageSpec_imageNotNull() {\n    String data =\n        \"image: null\\n\"\n            + \"platforms:\\n\" // trivial platform spec\n            + \"  - architecture: amd64\\n\"\n            + \"    os: linux\\n\";\n    try {\n      mapper.readValue(data, BaseImageSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'image' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testBaseImageSpec_imageNotEmpty() {\n    String data =\n        \"image: ''\\n\"\n            + \"platforms:\\n\" // trivial platform spec\n            + \"  - architecture: amd64\\n\"\n            + \"    os: linux\\n\";\n    try {\n      mapper.readValue(data, BaseImageSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'image' cannot be an empty string\"));\n    }\n  }\n\n  @Test\n  public void testBaseImageSpec_nullCollections() throws JsonProcessingException {\n    String data = \"image: gcr.io/example/jib\\n\";\n\n    BaseImageSpec baseImageSpec = mapper.readValue(data, BaseImageSpec.class);\n    Assert.assertEquals(ImmutableList.of(), baseImageSpec.getPlatforms());\n  }\n\n  @Test\n  public void testBaseImageSpec_platformsNoNullEntries() {\n    String data = \"image: gcr.io/example/jib\\n\" + \"platforms: [null]\\n\";\n\n    try {\n      mapper.readValue(data, BaseImageSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'platforms' cannot contain null entries\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFileSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.cloud.tools.jib.api.Ports;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.common.collect.ImmutableList;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link BuildFileSpec}. */\n@RunWith(JUnitParamsRunner.class)\npublic class BuildFileSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testBuildFileSpec_full() throws JsonProcessingException {\n    String data =\n        \"apiVersion: v1alpha1\\n\"\n            + \"kind: BuildFile\\n\"\n            + \"from:\\n\" // trivial base image spec\n            + \"  image: gcr.io/example/jib\\n\"\n            + \"creationTime: 1\\n\"\n            + \"format: OCI\\n\"\n            + \"environment:\\n\"\n            + \"  env_key: env_value\\n\"\n            + \"labels:\\n\"\n            + \"  label_key: label_value\\n\"\n            + \"volumes:\\n\"\n            + \"   - /my/volume\\n\"\n            + \"exposedPorts:\\n\"\n            + \"  - 8080\\n\"\n            + \"user: username\\n\"\n            + \"workingDirectory: /workspace\\n\"\n            + \"entrypoint:\\n\"\n            + \"  - java\\n\"\n            + \"  - -jar\\n\"\n            + \"cmd:\\n\"\n            + \"  - myjar.jar\\n\"\n            + \"layers:\\n\" // trivial layers\n            + \"  entries:\\n\"\n            + \"    - name: some layer\\n\"\n            + \"      archive: /something.tgz\\n\";\n\n    BuildFileSpec parsed = mapper.readValue(data, BuildFileSpec.class);\n    assertThat(parsed.getApiVersion()).isEqualTo(\"v1alpha1\");\n    assertThat(parsed.getKind()).isEqualTo(\"BuildFile\");\n    assertThat(parsed.getFrom().get().getImage()).isEqualTo(\"gcr.io/example/jib\");\n    assertThat(parsed.getCreationTime().get()).isEqualTo(Instant.ofEpochMilli(1));\n    assertThat(parsed.getFormat().get()).isEqualTo(ImageFormat.OCI);\n    assertThat(parsed.getEnvironment()).containsExactly(\"env_key\", \"env_value\");\n    assertThat(parsed.getLabels()).containsExactly(\"label_key\", \"label_value\");\n    assertThat(parsed.getVolumes()).containsExactly(AbsoluteUnixPath.get(\"/my/volume\"));\n    assertThat(parsed.getExposedPorts()).isEqualTo(Ports.parse(ImmutableList.of(\"8080\")));\n    assertThat(parsed.getUser().get()).isEqualTo(\"username\");\n    assertThat(parsed.getWorkingDirectory().get()).isEqualTo(AbsoluteUnixPath.get(\"/workspace\"));\n    assertThat(parsed.getEntrypoint().get()).containsExactly(\"java\", \"-jar\").inOrder();\n    assertThat(parsed.getCmd().get()).containsExactly(\"myjar.jar\");\n    assertThat(((ArchiveLayerSpec) parsed.getLayers().get().getEntries().get(0)).getName())\n        .isEqualTo(\"some layer\");\n    assertThat(((ArchiveLayerSpec) parsed.getLayers().get().getEntries().get(0)).getArchive())\n        .isEqualTo(Paths.get(\"/something.tgz\"));\n  }\n\n  @Test\n  public void testBuildFileSpec_apiVersionRequired() {\n    String data = \"kind: BuildFile\\n\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\"Missing required creator property 'apiVersion'\");\n  }\n\n  @Test\n  public void testBuildFileSpec_apiVersionNotNull() {\n    String data = \"apiVersion: null\\n\" + \"kind: BuildFile\\n\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception).hasMessageThat().contains(\"Property 'apiVersion' cannot be null\");\n  }\n\n  @Test\n  public void testBuildFileSpec_apiVersionNotEmpty() {\n    String data = \"apiVersion: ''\\n\" + \"kind: BuildFile\\n\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"Property 'apiVersion' cannot be an empty string\");\n  }\n\n  @Test\n  public void testBuildFileSpec_kindRequired() {\n    String data = \"apiVersion: v1alpha1\\n\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception).hasMessageThat().startsWith(\"Missing required creator property 'kind'\");\n  }\n\n  @Test\n  public void testBuildFileSpec_kindMustBeBuildFile() {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: NotBuildFile\\n\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"Property 'kind' must be 'BuildFile' but is 'NotBuildFile'\");\n  }\n\n  @Test\n  public void testBuildFileSpec_kindNotNull() {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: null\\n\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception).hasMessageThat().contains(\"Property 'kind' cannot be null\");\n  }\n\n  @Test\n  public void testBuildFileSpec_nullCollections() throws JsonProcessingException {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\";\n\n    BuildFileSpec parsed = mapper.readValue(data, BuildFileSpec.class);\n    assertThat(parsed.getEnvironment()).isEmpty();\n    assertThat(parsed.getLabels()).isEmpty();\n    assertThat(parsed.getVolumes()).isEmpty();\n    assertThat(parsed.getExposedPorts()).isEmpty();\n    // entrypoint and cmd CAN be not present\n    assertThat(parsed.getEntrypoint()).isEmpty();\n    assertThat(parsed.getCmd()).isEmpty();\n  }\n\n  @Test\n  @Parameters(value = {\"volumes\", \"exposedPorts\", \"entrypoint\", \"cmd\"})\n  public void testBuildFileSpec_noNullEntries(String fieldName) {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \": ['first', null]\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"Property '\" + fieldName + \"' cannot contain null entries\");\n  }\n\n  @Test\n  @Parameters(value = {\"volumes\", \"exposedPorts\", \"entrypoint\", \"cmd\"})\n  public void testBuildFileSpec_noEmptyEntries(String fieldName) {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \": ['first', ' ']\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"Property '\" + fieldName + \"' cannot contain empty strings\");\n  }\n\n  @Test\n  @Parameters(value = {\"volumes\", \"exposedPorts\", \"entrypoint\", \"cmd\"})\n  public void testBuildFileSpec_emptyListOkay(String fieldName) throws JsonProcessingException {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \": []\";\n\n    assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull();\n  }\n\n  @Test\n  @Parameters(\n      value = {\n        \"volumes\",\n        \"exposedPorts\",\n        \"entrypoint\",\n        \"cmd\",\n        \"creationTime\",\n        \"format\",\n        \"user\",\n        \"workingDirectory\",\n        \"environment\",\n        \"labels\"\n      })\n  public void testBuildFileSpec_nullOkay(String fieldName) throws JsonProcessingException {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \": null\";\n\n    assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull();\n  }\n\n  @Test\n  @Parameters(value = {\"creationTime\", \"format\", \"user\", \"workingDirectory\"})\n  public void testBuildFileSpec_noEmptyValues(String fieldName) {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \": ' '\";\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"Property '\" + fieldName + \"' cannot be an empty string\");\n  }\n\n  @SuppressWarnings(\"unused\")\n  private static String[][] invalidMapEntries() {\n    return new String[][] {\n      {\"environment\", \"  key: null\", \"' cannot contain null values\"},\n      {\"environment\", \"  key: ' '\", \"' cannot contain empty string values\"},\n      {\"environment\", \"  ' ': value\", \"' cannot contain empty string keys\"},\n      {\"labels\", \"  key: null\", \"' cannot contain null values\"},\n      {\"labels\", \"  key: ' '\", \"' cannot contain empty string values\"},\n      {\"labels\", \"  ' ': value\", \"' cannot contain empty string keys\"},\n    };\n  }\n\n  @Test\n  @Parameters(method = \"invalidMapEntries\")\n  public void testBuildFileSpec_invalidMapEntries(\n      String fieldName, String input, String errorMessage) {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \":\\n\" + input;\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class));\n    assertThat(exception).hasMessageThat().contains(\"Property '\" + fieldName + errorMessage);\n  }\n\n  // A quirk of our parser is that \"null\" keys are parsed as strings and not null, this test just\n  // formalizes that behavior.\n  @Test\n  @Parameters(value = {\"environment\", \"labels\"})\n  public void testBuildFileSpec_yamlNullKeysPass(String fieldName) throws JsonProcessingException {\n    String data =\n        \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \":\\n\" + \"  null: value\";\n\n    assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull();\n  }\n\n  @Test\n  @Parameters(value = {\"environment\", \"labels\"})\n  public void testBuildFileSpec_emptyMapOkay(String fieldName) throws JsonProcessingException {\n    String data = \"apiVersion: v1alpha1\\n\" + \"kind: BuildFile\\n\" + fieldName + \": {}\";\n\n    assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFilesTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.LayerObject;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.cli.Build;\nimport com.google.cloud.tools.jib.cli.CommonCliOptions;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Map;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\n\npublic class BuildFilesTest {\n\n  @Rule public final TemporaryFolder tmp = new TemporaryFolder();\n  @Rule public final MockitoRule rule = MockitoJUnit.rule();\n\n  @Mock private ConsoleLogger consoleLogger;\n  @Mock private Build buildCli;\n  @Mock private CommonCliOptions commonCliOptions;\n\n  @Before\n  public void setUp() {\n    Mockito.when(buildCli.getTemplateParameters()).thenReturn(ImmutableMap.of());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_allProperties()\n      throws URISyntaxException, IOException, InvalidImageReferenceException {\n    Path buildfile =\n        Paths.get(Resources.getResource(\"buildfiles/projects/allProperties/jib.yaml\").toURI());\n    Path projectRoot = buildfile.getParent();\n    JibContainerBuilder jibContainerBuilder =\n        BuildFiles.toJibContainerBuilder(\n            projectRoot, buildfile, buildCli, commonCliOptions, consoleLogger);\n\n    ContainerBuildPlan resolved = jibContainerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(\"ubuntu\", resolved.getBaseImage());\n    Assert.assertEquals(Instant.ofEpochMilli(2000), resolved.getCreationTime());\n    Assert.assertEquals(ImageFormat.OCI, resolved.getFormat());\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"arm\", \"linux\"), new Platform(\"amd64\", \"darwin\")),\n        resolved.getPlatforms());\n    Assert.assertEquals(ImmutableMap.of(\"KEY1\", \"v1\", \"KEY2\", \"v2\"), resolved.getEnvironment());\n    Assert.assertEquals(\n        ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")),\n        resolved.getVolumes());\n    Assert.assertEquals(ImmutableMap.of(\"label1\", \"l1\", \"label2\", \"l2\"), resolved.getLabels());\n    Assert.assertEquals(\n        ImmutableSet.of(Port.udp(123), Port.tcp(456), Port.tcp(789)), resolved.getExposedPorts());\n    Assert.assertEquals(\"customUser\", resolved.getUser());\n    Assert.assertEquals(AbsoluteUnixPath.get(\"/home\"), resolved.getWorkingDirectory());\n    Assert.assertEquals(ImmutableList.of(\"sh\", \"script.sh\"), resolved.getEntrypoint());\n    Assert.assertEquals(ImmutableList.of(\"--param\", \"param\"), resolved.getCmd());\n\n    Assert.assertEquals(1, resolved.getLayers().size());\n    FileEntriesLayer resolvedLayer = (FileEntriesLayer) resolved.getLayers().get(0);\n    Assert.assertEquals(\"scripts\", resolvedLayer.getName());\n    Assert.assertEquals(\n        FileEntriesLayer.builder()\n            .addEntry(\n                projectRoot.resolve(\"project/script.sh\"), AbsoluteUnixPath.get(\"/home/script.sh\"))\n            .build()\n            .getEntries(),\n        resolvedLayer.getEntries());\n    Assert.assertEquals(LayerObject.Type.FILE_ENTRIES, resolvedLayer.getType());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_requiredProperties()\n      throws URISyntaxException, IOException, InvalidImageReferenceException {\n    Path buildfile =\n        Paths.get(Resources.getResource(\"buildfiles/projects/allDefaults/jib.yaml\").toURI());\n    JibContainerBuilder jibContainerBuilder =\n        BuildFiles.toJibContainerBuilder(\n            buildfile.getParent(), buildfile, buildCli, commonCliOptions, consoleLogger);\n\n    ContainerBuildPlan resolved = jibContainerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(\"scratch\", resolved.getBaseImage());\n    Assert.assertEquals(ImmutableSet.of(new Platform(\"amd64\", \"linux\")), resolved.getPlatforms());\n    Assert.assertEquals(Instant.EPOCH, resolved.getCreationTime());\n    Assert.assertEquals(ImageFormat.Docker, resolved.getFormat());\n    Assert.assertTrue(resolved.getEnvironment().isEmpty());\n    Assert.assertTrue(resolved.getLabels().isEmpty());\n    Assert.assertTrue(resolved.getVolumes().isEmpty());\n    Assert.assertTrue(resolved.getExposedPorts().isEmpty());\n    Assert.assertNull(resolved.getUser());\n    Assert.assertNull(resolved.getWorkingDirectory());\n    Assert.assertNull(resolved.getEntrypoint());\n    Assert.assertTrue(resolved.getLayers().isEmpty());\n  }\n\n  @Test\n  public void testToBuildFileSpec_withTemplating()\n      throws URISyntaxException, InvalidImageReferenceException, IOException {\n    Path buildfile =\n        Paths.get(Resources.getResource(\"buildfiles/projects/templating/valid.yaml\").toURI());\n\n    Mockito.when(buildCli.getTemplateParameters())\n        .thenReturn(\n            ImmutableMap.of(\n                \"unused\", \"ignored\", // keys that are defined but not used do not throw an error\n                \"key\", \"templateKey\",\n                \"value\", \"templateValue\",\n                \"repeated\", \"repeatedValue\"));\n    JibContainerBuilder jibContainerBuilder =\n        BuildFiles.toJibContainerBuilder(\n            buildfile.getParent(), buildfile, buildCli, commonCliOptions, consoleLogger);\n\n    ContainerBuildPlan resolved = jibContainerBuilder.toContainerBuildPlan();\n    Map<String, String> expectedLabels =\n        ImmutableMap.<String, String>builder()\n            .put(\"templateKey\", \"templateValue\")\n            .put(\"label1\", \"repeatedValue\")\n            .put(\"label2\", \"repeatedValue\")\n            .put(\"label3\", \"${escaped}\")\n            .put(\"label4\", \"free$\")\n            .put(\"unmatched\", \"${\")\n            .build();\n    Assert.assertEquals(expectedLabels, resolved.getLabels());\n  }\n\n  @Test\n  public void testToBuildFileSpec_failWithMissingTemplateVariable()\n      throws URISyntaxException, InvalidImageReferenceException, IOException {\n    Path buildfile =\n        Paths.get(Resources.getResource(\"buildfiles/projects/templating/missingVar.yaml\").toURI());\n\n    try {\n      BuildFiles.toJibContainerBuilder(\n          buildfile.getParent(), buildfile, buildCli, commonCliOptions, consoleLogger);\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      MatcherAssert.assertThat(\n          iae.getMessage(), CoreMatchers.startsWith(\"Cannot resolve variable 'missingVar'\"));\n    }\n  }\n\n  @Test\n  public void testToBuildFileSpec_templateMultiLineBehavior()\n      throws URISyntaxException, InvalidImageReferenceException, IOException {\n    Path buildfile =\n        Paths.get(Resources.getResource(\"buildfiles/projects/templating/multiLine.yaml\").toURI());\n\n    Mockito.when(buildCli.getTemplateParameters())\n        .thenReturn(ImmutableMap.of(\"replace\" + \"\\n\" + \"this\", \"creationTime: 1234\"));\n    JibContainerBuilder jibContainerBuilder =\n        BuildFiles.toJibContainerBuilder(\n            buildfile.getParent(), buildfile, buildCli, commonCliOptions, consoleLogger);\n    ContainerBuildPlan resolved = jibContainerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(Instant.ofEpochMilli(1234), resolved.getCreationTime());\n  }\n\n  @Test\n  public void testToBuildFileSpec_alternativeRootContext()\n      throws URISyntaxException, InvalidImageReferenceException, IOException {\n    Path buildfile =\n        Paths.get(\n            Resources.getResource(\"buildfiles/projects/allProperties/altYamls/alt-jib.yaml\")\n                .toURI());\n    Path projectRoot = buildfile.getParent().getParent();\n    JibContainerBuilder jibContainerBuilder =\n        BuildFiles.toJibContainerBuilder(\n            projectRoot, buildfile, buildCli, commonCliOptions, consoleLogger);\n\n    ContainerBuildPlan resolved = jibContainerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(\n        FileEntriesLayer.builder()\n            .addEntry(\n                projectRoot.resolve(\"project/script.sh\"), AbsoluteUnixPath.get(\"/home/script.sh\"))\n            .build()\n            .getEntries(),\n        ((FileEntriesLayer) resolved.getLayers().get(0)).getEntries());\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/CopySpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link CopySpec}. */\n@RunWith(JUnitParamsRunner.class)\npublic class CopySpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testCopySpec_full() throws JsonProcessingException {\n    String data =\n        \"src: target/classes\\n\"\n            + \"dest: /app/classes\\n\"\n            + \"includes:\\n\"\n            + \"  - '**/*.in'\\n\"\n            + \"excludes:\\n\"\n            + \"  - '**/*.ex'\\n\"\n            + \"properties:\\n\" // only trivial test of file properties\n            + \"  timestamp: 1\\n\";\n\n    CopySpec parsed = mapper.readValue(data, CopySpec.class);\n    assertThat(parsed.getSrc()).isEqualTo(Paths.get(\"target/classes\"));\n    assertThat(parsed.getDest()).isEqualTo(AbsoluteUnixPath.get(\"/app/classes\"));\n    assertThat(parsed.getIncludes()).containsExactly(\"**/*.in\");\n    assertThat(parsed.getExcludes()).containsExactly(\"**/*.ex\");\n    assertThat(parsed.getProperties().get().getTimestamp().get())\n        .isEqualTo(Instant.ofEpochMilli(1));\n  }\n\n  @Test\n  @Parameters(\n      value = {\n        \"dest: /app/classes\\n, Missing required creator property 'src'\",\n        \"src: target/classes\\n, Missing required creator property 'dest'\"\n      })\n  public void testCopySpec_required(String data, String errorMessage) {\n    Exception exception =\n        assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class));\n    assertThat(exception).hasMessageThat().startsWith(errorMessage);\n  }\n\n  @Test\n  public void testCopySpec_destEndsWithSlash() throws JsonProcessingException {\n    String data = \"src: target/classes\\n\" + \"dest: /app/classes/\";\n\n    CopySpec parsed = mapper.readValue(data, CopySpec.class);\n    assertThat(parsed.getDest()).isEqualTo(AbsoluteUnixPath.get(\"/app/classes\"));\n    assertThat(parsed.isDestEndsWithSlash()).isTrue();\n  }\n\n  @Test\n  public void testCopySpec_destDoesNotEndWithSlash() throws JsonProcessingException {\n    String data = \"src: target/classes\\n\" + \"dest: /app/classes\";\n\n    CopySpec parsed = mapper.readValue(data, CopySpec.class);\n    assertThat(parsed.getDest()).isEqualTo(AbsoluteUnixPath.get(\"/app/classes\"));\n    assertThat(parsed.isDestEndsWithSlash()).isFalse();\n  }\n\n  @Test\n  @Parameters(\n      value = {\n        \"src: null\\ndest: /app/classes\\n, Property 'src' cannot be null\",\n        \"src: ''\\ndest: /app/classes\\n, Property 'src' cannot be an empty string\",\n        \"src: target/classes\\ndest: null\\n, Property 'dest' cannot be null\",\n        \"src: target/classes\\ndest: ''\\n, Property 'dest' cannot be an empty string\"\n      })\n  public void testCopySpec_nullEmptyCheck(String data, String errorMessage) {\n    Exception exception =\n        assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class));\n    assertThat(exception).hasMessageThat().contains(errorMessage);\n  }\n\n  @Test\n  public void testCopySpec_nullCollections() throws JsonProcessingException {\n    String data = \"src: target/classes\\n\" + \"dest: /app/classes\\n\";\n\n    CopySpec parsed = mapper.readValue(data, CopySpec.class);\n    assertThat(parsed.getIncludes()).isEmpty();\n    assertThat(parsed.getExcludes()).isEmpty();\n  }\n\n  @Test\n  @Parameters(value = {\"includes\", \"excludes\"})\n  public void testCopySpec_noNullEntries(String fieldName) {\n    String data =\n        \"src: target/classes\\n\" + \"dest: /app/classes\\n\" + fieldName + \": ['first', null]\";\n\n    Exception exception =\n        assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class));\n    assertThat(exception)\n        .hasCauseThat()\n        .hasMessageThat()\n        .isEqualTo(\"Property '\" + fieldName + \"' cannot contain null entries\");\n  }\n\n  @Test\n  @Parameters(value = {\"includes\", \"excludes\"})\n  public void testCopySpec_noEmptyEntries(String fieldName) {\n    String data = \"src: target/classes\\n\" + \"dest: /app/classes\\n\" + fieldName + \": ['first', ' ']\";\n\n    Exception exception =\n        assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class));\n    assertThat(exception)\n        .hasCauseThat()\n        .hasMessageThat()\n        .isEqualTo(\"Property '\" + fieldName + \"' cannot contain empty strings\");\n  }\n\n  @Test\n  @Parameters(value = {\"includes\", \"excludes\"})\n  public void testCopySpec_emptyOkay(String fieldName) throws JsonProcessingException {\n    String data = \"src: target/classes\\n\" + \"dest: /app/classes\\n\" + fieldName + \": []\";\n\n    assertThat(mapper.readValue(data, CopySpec.class)).isNotNull();\n  }\n\n  @Test\n  @Parameters(value = {\"includes\", \"excludes\"})\n  public void testCopySpec_nullOkay(String fieldName) throws JsonProcessingException {\n    String data = \"src: target/classes\\n\" + \"dest: /app/classes\\n\" + fieldName + \": null\";\n\n    assertThat(mapper.readValue(data, CopySpec.class)).isNotNull();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FileLayerSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link FileLayerSpec}. */\npublic class FileLayerSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testFileLayerSpec_full() throws JsonProcessingException {\n    String data =\n        \"name: layer name\\n\"\n            + \"files:\\n\" // trivial copy spec\n            + \"  - src: source\\n\"\n            + \"    dest: /dest\\n\"\n            + \"properties:\\n\" // trivial file properties spec\n            + \"  timestamp: 1\\n\";\n\n    FileLayerSpec parsed = mapper.readValue(data, FileLayerSpec.class);\n    Assert.assertEquals(\"layer name\", parsed.getName());\n    Assert.assertEquals(Paths.get(\"source\"), parsed.getFiles().get(0).getSrc());\n    Assert.assertEquals(AbsoluteUnixPath.get(\"/dest\"), parsed.getFiles().get(0).getDest());\n    Assert.assertEquals(Instant.ofEpochMilli(1), parsed.getProperties().get().getTimestamp().get());\n  }\n\n  @Test\n  public void testFileLayerSpec_nameRequired() {\n    String data = \"files:\\n\" + \"  - src: source\\n\" + \"    dest: /dest\\n\";\n\n    try {\n      mapper.readValue(data, FileLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'name'\"));\n    }\n  }\n\n  @Test\n  public void testFileLayerSpec_nameNotNull() {\n    String data = \"name: null\\n\" + \"files:\\n\" + \"  - src: source\\n\" + \"    dest: /dest\\n\";\n\n    try {\n      mapper.readValue(data, FileLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'name' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testFileLayerSpec_nameNotEmpty() {\n    String data = \"name: ''\\n\" + \"files:\\n\" + \"  - src: source\\n\" + \"    dest: /dest\\n\";\n\n    try {\n      mapper.readValue(data, FileLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'name' cannot be an empty string\"));\n    }\n  }\n\n  @Test\n  public void testFileLayerSpec_filesRequired() {\n    String data = \"name: layer name\";\n\n    try {\n      mapper.readValue(data, FileLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'files'\"));\n    }\n  }\n\n  @Test\n  public void testFileLayerSpec_filesNotNull() {\n    String data = \"name: layer name\\n\" + \"files: null\";\n\n    try {\n      mapper.readValue(data, FileLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'files' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testFileLayerSpec_filesNotEmpty() {\n    String data = \"name: layer name\\n\" + \"files: []\\n\";\n\n    try {\n      mapper.readValue(data, FileLayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'files' cannot be an empty collection\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonMappingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport java.time.Instant;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n/** Tests for {@link FilePropertiesSpec}. */\n@RunWith(JUnitParamsRunner.class)\npublic class FilePropertiesSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testFilePropertiesSpec_full() throws JsonProcessingException {\n    String data =\n        \"filePermissions: 644\\n\"\n            + \"directoryPermissions: 755\\n\"\n            + \"user: goose\\n\"\n            + \"group: birds\\n\"\n            + \"timestamp: 1\\n\";\n\n    FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class);\n    assertThat(parsed.getFilePermissions().get()).isEqualTo(FilePermissions.fromOctalString(\"644\"));\n    assertThat(parsed.getDirectoryPermissions().get())\n        .isEqualTo(FilePermissions.fromOctalString(\"755\"));\n    assertThat(parsed.getUser().get()).isEqualTo(\"goose\");\n    assertThat(parsed.getGroup().get()).isEqualTo(\"birds\");\n    assertThat(parsed.getTimestamp().get()).isEqualTo(Instant.ofEpochMilli(1));\n  }\n\n  @Test\n  public void testFilePropertiesSpec_badFilePermissions() {\n    String data = \"filePermissions: 888\";\n\n    Exception exception =\n        assertThrows(\n            JsonMappingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class));\n    assertThat(exception)\n        .hasCauseThat()\n        .hasMessageThat()\n        .isEqualTo(\"octalPermissions must be a 3-digit octal number (000-777)\");\n  }\n\n  @Test\n  public void testFilePropertiesSpec_badDirectoryPermissions() {\n    String data = \"directoryPermissions: 888\";\n\n    Exception exception =\n        assertThrows(\n            JsonMappingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class));\n    assertThat(exception)\n        .hasCauseThat()\n        .hasMessageThat()\n        .isEqualTo(\"octalPermissions must be a 3-digit octal number (000-777)\");\n  }\n\n  @Test\n  public void testFilePropertiesSpec_timestampSpecIso8601() throws JsonProcessingException {\n    String data = \"timestamp: 2020-06-08T14:54:36+00:00\";\n\n    FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class);\n    assertThat(parsed.getTimestamp().get()).isEqualTo(Instant.parse(\"2020-06-08T14:54:36Z\"));\n  }\n\n  @Test\n  public void testFilePropertiesSpec_badTimestamp() {\n    String data = \"timestamp: hi\";\n\n    Exception exception =\n        assertThrows(\n            JsonMappingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class));\n    assertThat(exception)\n        .hasCauseThat()\n        .hasMessageThat()\n        .isEqualTo(\n            \"timestamp must be a number of milliseconds since epoch or an ISO 8601 formatted date\");\n  }\n\n  @Test\n  public void testFilePropertiesSpec_failOnUnknown() {\n    String data = \"badkey: badvalue\";\n\n    Exception exception =\n        assertThrows(\n            UnrecognizedPropertyException.class,\n            () -> mapper.readValue(data, FilePropertiesSpec.class));\n    assertThat(exception).hasMessageThat().contains(\"Unrecognized field \\\"badkey\\\"\");\n  }\n\n  @Test\n  @Parameters(value = {\"filePermissions\", \"directoryPermissions\", \"user\", \"group\", \"timestamp\"})\n  public void testFilePropertiesSpec_noEmptyValues(String fieldName) {\n    String data = fieldName + \": ' '\";\n\n    Exception exception =\n        assertThrows(\n            JsonProcessingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class));\n    assertThat(exception)\n        .hasCauseThat()\n        .hasMessageThat()\n        .isEqualTo(\"Property '\" + fieldName + \"' cannot be an empty string\");\n  }\n\n  @Test\n  @Parameters(value = {\"filePermissions\", \"directoryPermissions\", \"user\", \"group\", \"timestamp\"})\n  public void testFilePropertiesSpec_nullOkay(String fieldName) throws JsonProcessingException {\n    String data = fieldName + \": null\";\n\n    FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class);\n    assertThat(parsed.getFilePermissions().isPresent()).isFalse();\n    assertThat(parsed.getDirectoryPermissions().isPresent()).isFalse();\n    assertThat(parsed.getUser().isPresent()).isFalse();\n    assertThat(parsed.getGroup().isPresent()).isFalse();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesStackTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport java.time.Instant;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class FilePropertiesStackTest {\n\n  @Test\n  public void testDefaults() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    Assert.assertEquals(FilePermissions.fromOctalString(\"644\"), testStack.getFilePermissions());\n    Assert.assertEquals(\n        FilePermissions.fromOctalString(\"755\"), testStack.getDirectoryPermissions());\n    Assert.assertEquals(\"\", testStack.getOwnership());\n    Assert.assertEquals(Instant.ofEpochSecond(1), testStack.getModificationTime());\n  }\n\n  @Test\n  public void testPush_simple() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n\n    Assert.assertEquals(FilePermissions.fromOctalString(\"111\"), testStack.getFilePermissions());\n    Assert.assertEquals(\n        FilePermissions.fromOctalString(\"111\"), testStack.getDirectoryPermissions());\n    Assert.assertEquals(\"1:1\", testStack.getOwnership());\n    Assert.assertEquals(Instant.ofEpochMilli(1), testStack.getModificationTime());\n  }\n\n  @Test\n  public void testPush_stacking() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n    testStack.push(new FilePropertiesSpec(null, \"222\", \"2\", null, null));\n    testStack.push(new FilePropertiesSpec(\"333\", null, \"3\", null, \"3\"));\n\n    Assert.assertEquals(FilePermissions.fromOctalString(\"333\"), testStack.getFilePermissions());\n    Assert.assertEquals(\n        FilePermissions.fromOctalString(\"222\"), testStack.getDirectoryPermissions());\n    Assert.assertEquals(\"3:1\", testStack.getOwnership());\n    Assert.assertEquals(Instant.ofEpochMilli(3), testStack.getModificationTime());\n  }\n\n  @Test\n  public void testPush_tooMany() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n\n    try {\n      testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n      Assert.fail();\n    } catch (IllegalStateException ise) {\n      Assert.assertEquals(\"Error in file properties stack push, stacking over 3\", ise.getMessage());\n    }\n  }\n\n  @Test\n  public void testPop_toZero() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n    testStack.pop();\n\n    Assert.assertEquals(FilePermissions.fromOctalString(\"644\"), testStack.getFilePermissions());\n    Assert.assertEquals(\n        FilePermissions.fromOctalString(\"755\"), testStack.getDirectoryPermissions());\n    Assert.assertEquals(\"\", testStack.getOwnership());\n    Assert.assertEquals(Instant.ofEpochSecond(1), testStack.getModificationTime());\n  }\n\n  @Test\n  public void testPop_toOlderState() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    testStack.push(new FilePropertiesSpec(\"111\", \"111\", \"1\", \"1\", \"1\"));\n    testStack.push(new FilePropertiesSpec(null, \"222\", \"2\", null, null));\n\n    testStack.pop();\n\n    Assert.assertEquals(FilePermissions.fromOctalString(\"111\"), testStack.getFilePermissions());\n    Assert.assertEquals(\n        FilePermissions.fromOctalString(\"111\"), testStack.getDirectoryPermissions());\n    Assert.assertEquals(\"1:1\", testStack.getOwnership());\n    Assert.assertEquals(Instant.ofEpochMilli(1), testStack.getModificationTime());\n  }\n\n  @Test\n  public void testPop_nothingToPop() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n\n    try {\n      testStack.pop();\n      Assert.fail();\n    } catch (IllegalStateException ise) {\n      Assert.assertEquals(\"Error in file properties stack pop, popping at 0\", ise.getMessage());\n    }\n  }\n\n  @Test\n  public void testGetOwnership_onlyUser() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n    testStack.push(new FilePropertiesSpec(null, null, \"u\", null, null));\n\n    Assert.assertEquals(\"u\", testStack.getOwnership());\n  }\n\n  @Test\n  public void testGetOwnership_onlyGroup() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n    testStack.push(new FilePropertiesSpec(null, null, null, \"g\", null));\n\n    Assert.assertEquals(\":g\", testStack.getOwnership());\n  }\n\n  @Test\n  public void testGetOwnership_userAndGroup() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n    testStack.push(new FilePropertiesSpec(null, null, \"u\", \"g\", null));\n\n    Assert.assertEquals(\"u:g\", testStack.getOwnership());\n  }\n\n  @Test\n  public void testGetOwnership_noUserNoGroup() {\n    FilePropertiesStack testStack = new FilePropertiesStack();\n    testStack.push(new FilePropertiesSpec(null, null, null, null, null));\n\n    Assert.assertEquals(\"\", testStack.getOwnership());\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/LayerSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link LayerSpec}. */\npublic class LayerSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void deserialize_toFileLayer() throws JsonProcessingException {\n    String data =\n        \"name: layer name\\n\"\n            + \"files:\\n\" // trivial copy spec\n            + \"  - src: source\\n\"\n            + \"    dest: /dest\\n\";\n\n    LayerSpec layerSpec = mapper.readValue(data, LayerSpec.class);\n    MatcherAssert.assertThat(layerSpec, CoreMatchers.instanceOf(FileLayerSpec.class));\n  }\n\n  @Test\n  public void deserialize_toArchiveLayer() throws JsonProcessingException {\n    String data = \"name: layer name\\n\" + \"archive: out/archive.tgz\\n\";\n\n    LayerSpec layerSpec = mapper.readValue(data, LayerSpec.class);\n    MatcherAssert.assertThat(layerSpec, CoreMatchers.instanceOf(ArchiveLayerSpec.class));\n  }\n\n  @Test\n  public void deserialize_error() {\n    String data = \"name: layer name\\n\";\n\n    try {\n      mapper.readValue(data, LayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.endsWith(\"Could not parse entry into ArchiveLayer or FileLayer\"));\n    }\n  }\n\n  @Test\n  public void deserialize_nameMissing() {\n    String data = \"archive: out/archive.tgz\\n\";\n\n    try {\n      mapper.readValue(data, LayerSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.endsWith(\"Could not parse layer entry, missing required property 'name'\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/LayersSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link LayersSpec}. */\npublic class LayersSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testLayersSpec_full() throws JsonProcessingException {\n    String data =\n        \"entries:\\n\" // trivial layer\n            + \"  - name: some layer\\n\"\n            + \"    archive: /something.tgz\\n\"\n            + \"properties:\\n\" // trivial file properties spec\n            + \"  timestamp: 1\\n\";\n\n    LayersSpec parsed = mapper.readValue(data, LayersSpec.class);\n    Assert.assertEquals(\"some layer\", ((ArchiveLayerSpec) parsed.getEntries().get(0)).getName());\n    Assert.assertEquals(\n        Paths.get(\"/something.tgz\"), ((ArchiveLayerSpec) parsed.getEntries().get(0)).getArchive());\n    Assert.assertEquals(Instant.ofEpochMilli(1), parsed.getProperties().get().getTimestamp().get());\n  }\n\n  @Test\n  public void testLayersSpec_entriesRequired() {\n    String data =\n        \"properties:\\n\" // trivial file properties spec\n            + \"  timestamp: 1\\n\";\n\n    try {\n      mapper.readValue(data, LayersSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'entries'\"));\n    }\n  }\n\n  @Test\n  public void testLayersSpec_entriesNotNull() {\n    String data =\n        \"entries: null\\n\"\n            + \"properties:\\n\" // trivial file properties spec\n            + \"  timestamp: 1\\n\";\n\n    try {\n      mapper.readValue(data, LayersSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'entries' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testLayersSpec_entriesNotEmpty() {\n    String data =\n        \"entries: []\\n\"\n            + \"properties:\\n\" // trivial file properties spec\n            + \"  timestamp: 1\\n\";\n\n    try {\n      mapper.readValue(data, LayersSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'entries' cannot be an empty collection\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/LayersTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.common.base.Charsets;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Layers}. */\npublic class LayersTest {\n\n  private static final String LAYERS_TEST_RESOURCE_DIR = \"buildfiles/layers/\";\n\n  public static List<FileEntriesLayer> parseLayers(Path testDir, int expectedLayerCount)\n      throws IOException {\n    Path layersSpecYaml = testDir.resolve(\"layers.yaml\");\n    List<FileEntriesLayer> layers =\n        Layers.toLayers(\n            layersSpecYaml.getParent(),\n            new ObjectMapper(new YAMLFactory())\n                .readValue(\n                    Files.newBufferedReader(layersSpecYaml, Charsets.UTF_8), LayersSpec.class));\n\n    Assert.assertEquals(expectedLayerCount, layers.size());\n    return layers;\n  }\n\n  private static Path getLayersTestRoot(String testName) throws URISyntaxException {\n    return Paths.get(Resources.getResource(LAYERS_TEST_RESOURCE_DIR + testName).toURI());\n  }\n\n  @Test\n  public void testToLayers_properties() throws IOException, URISyntaxException {\n    Path testRoot = getLayersTestRoot(\"propertiesTest\");\n    List<FileEntriesLayer> layers = parseLayers(testRoot, 4);\n\n    checkLayer(\n        layers.get(0),\n        \"level 0 passthrough\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"dir\", \"/app\", \"700\", 0, \"0:0\"),\n            newEntry(testRoot, \"dir/file.txt\", \"/app/file.txt\", \"000\", 0, \"0:0\")));\n\n    checkLayer(\n        layers.get(1),\n        \"level 1 overrides\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"dir\", \"/app\", \"711\", 1000, \"1:1\"),\n            newEntry(testRoot, \"dir/file.txt\", \"/app/file.txt\", \"111\", 1000, \"1:1\")));\n\n    checkLayer(\n        layers.get(2),\n        \"level 2 overrides\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"dir\", \"/app\", \"722\", 2000, \"2:2\"),\n            newEntry(testRoot, \"dir/file.txt\", \"/app/file.txt\", \"222\", 2000, \"2:2\")));\n\n    checkLayer(\n        layers.get(3),\n        \"partial overrides\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"dir\", \"/app\", \"711\", 2000, \"0:2\"),\n            newEntry(testRoot, \"dir/file.txt\", \"/app/file.txt\", \"111\", 2000, \"0:2\")));\n  }\n\n  @Test\n  public void testToLayers_includeExcludes() throws IOException, URISyntaxException {\n    Path testRoot = getLayersTestRoot(\"includesExcludesTest\");\n    List<FileEntriesLayer> layers = parseLayers(testRoot, 6);\n\n    checkLayer(\n        layers.get(0),\n        \"includes and excludes\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"project\", \"/target/ie\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/includedDir/\", \"/target/ie/includedDir/\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/includedDir/include.me\",\n                \"/target/ie/includedDir/include.me\",\n                \"644\",\n                0,\n                \"0:0\")));\n\n    checkLayer(\n        layers.get(1),\n        \"includes only\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"project\", \"/target/io\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/includedDir/\", \"/target/io/includedDir/\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/includedDir/include.me\",\n                \"/target/io/includedDir/include.me\",\n                \"644\",\n                0,\n                \"0:0\")));\n\n    checkLayer(\n        layers.get(2),\n        \"excludes only\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"project\", \"/target/eo\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/excludedDir\", \"/target/eo/excludedDir\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/includedDir\", \"/target/eo/includedDir\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/includedDir/include.me\",\n                \"/target/eo/includedDir/include.me\",\n                \"644\",\n                0,\n                \"0:0\"),\n            newEntry(testRoot, \"project/wild.card\", \"/target/eo/wild.card\", \"644\", 0, \"0:0\")));\n\n    checkLayer(\n        layers.get(3),\n        \"excludes only shortcut\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"project\", \"/target/eo\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/excludedDir\", \"/target/eo/excludedDir\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/includedDir\", \"/target/eo/includedDir\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/includedDir/include.me\",\n                \"/target/eo/includedDir/include.me\",\n                \"644\",\n                0,\n                \"0:0\"),\n            newEntry(testRoot, \"project/wild.card\", \"/target/eo/wild.card\", \"644\", 0, \"0:0\")));\n\n    checkLayer(\n        layers.get(4),\n        \"exclude dir and contents\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"project\", \"/target/edac\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot, \"project/includedDir/\", \"/target/edac/includedDir/\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/includedDir/include.me\",\n                \"/target/edac/includedDir/include.me\",\n                \"644\",\n                0,\n                \"0:0\"),\n            newEntry(testRoot, \"project/wild.card\", \"/target/edac/wild.card\", \"644\", 0, \"0:0\")));\n\n    checkLayer(\n        layers.get(5),\n        \"excludes only wrong\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"project\", \"/target/eo\", \"755\", 0, \"0:0\"),\n            newEntry(testRoot, \"project/excludedDir\", \"/target/eo/excludedDir\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/excludedDir/exclude.me\",\n                \"/target/eo/excludedDir/exclude.me\",\n                \"644\",\n                0,\n                \"0:0\"),\n            newEntry(testRoot, \"project/includedDir\", \"/target/eo/includedDir\", \"755\", 0, \"0:0\"),\n            newEntry(\n                testRoot,\n                \"project/includedDir/include.me\",\n                \"/target/eo/includedDir/include.me\",\n                \"644\",\n                0,\n                \"0:0\"),\n            newEntry(testRoot, \"project/wild.card\", \"/target/eo/wild.card\", \"644\", 0, \"0:0\")));\n  }\n\n  @Test\n  public void testToLayers_file() throws IOException, URISyntaxException {\n    Path testRoot = getLayersTestRoot(\"fileTest/default\");\n    List<FileEntriesLayer> layers = parseLayers(testRoot, 1);\n\n    checkLayer(\n        layers.get(0),\n        \"default\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"toFile.txt\", \"/target/toFile.txt\", \"644\", 0, \"0:0\"),\n            newEntry(testRoot, \"toDir.txt\", \"/target/dir/toDir.txt\", \"644\", 0, \"0:0\")));\n  }\n\n  @Test\n  public void testToLayers_fileWithIncludes() throws IOException, URISyntaxException {\n    Path testRoot = getLayersTestRoot(\"fileTest/failWithIncludes\");\n    try {\n      parseLayers(testRoot, 0);\n      Assert.fail();\n    } catch (UnsupportedOperationException uoe) {\n      Assert.assertEquals(\n          \"Cannot apply includes/excludes on single file copy directives.\", uoe.getMessage());\n    }\n  }\n\n  @Test\n  public void testToLayers_fileWithExcludes() throws IOException, URISyntaxException {\n    Path testRoot = getLayersTestRoot(\"fileTest/failWithExcludes\");\n    try {\n      parseLayers(testRoot, 0);\n      Assert.fail();\n    } catch (UnsupportedOperationException uoe) {\n      Assert.assertEquals(\n          \"Cannot apply includes/excludes on single file copy directives.\", uoe.getMessage());\n    }\n  }\n\n  private static FileEntry newEntry(\n      Path testRoot,\n      String src,\n      String dest,\n      String octalPermissions,\n      int millis,\n      String ownership) {\n    return new FileEntry(\n        testRoot.resolve(src),\n        AbsoluteUnixPath.get(dest),\n        FilePermissions.fromOctalString(octalPermissions),\n        Instant.ofEpochMilli(millis),\n        ownership);\n  }\n\n  private static void checkLayer(\n      FileEntriesLayer layer, String expectedName, Set<FileEntry> expectedLayerEntries) {\n    Assert.assertEquals(expectedName, layer.getName());\n\n    try {\n      Assert.assertEquals(expectedLayerEntries, ImmutableSet.copyOf(layer.getEntries()));\n    } catch (AssertionError ae) {\n      System.out.println(\"ACTUAL\");\n      layer\n          .getEntries()\n          .forEach(\n              entry -> {\n                System.out.println(\"src: \" + entry.getSourceFile());\n                System.out.println(\"dest: \" + entry.getExtractionPath());\n                System.out.println(\"permission: \" + entry.getPermissions().toOctalString());\n                System.out.println(\"time: \" + entry.getModificationTime());\n                System.out.println(\"ownership: \" + entry.getOwnership());\n              });\n      System.out.println(\"EXCPECTED\");\n      expectedLayerEntries.forEach(\n          entry -> {\n            System.out.println(\"src: \" + entry.getSourceFile());\n            System.out.println(\"dest: \" + entry.getExtractionPath());\n            System.out.println(\"permission: \" + entry.getPermissions().toOctalString());\n            System.out.println(\"time: \" + entry.getModificationTime());\n            System.out.println(\"ownership: \" + entry.getOwnership());\n          });\n      throw ae;\n    }\n  }\n\n  @Test\n  public void testToLayers_pathDoesNotExist() throws IOException, URISyntaxException {\n    Path testRoot = getLayersTestRoot(\"pathDoesNotExist\");\n    try {\n      parseLayers(testRoot, 0);\n      Assert.fail();\n    } catch (UnsupportedOperationException uoe) {\n      Assert.assertEquals(\n          \"Cannot create FileLayers from non-file, non-directory: \"\n              + testRoot.resolve(\"something-that-does-not-exist\").toString(),\n          uoe.getMessage());\n    }\n  }\n\n  @Test\n  public void testToLayers_archiveLayersNotSupported() throws URISyntaxException, IOException {\n    Path testRoot = getLayersTestRoot(\"archiveLayerTest\");\n    try {\n      parseLayers(testRoot, 0);\n      Assert.fail();\n    } catch (UnsupportedOperationException uoe) {\n      Assert.assertEquals(\"Only FileLayers are supported at this time.\", uoe.getMessage());\n    }\n  }\n\n  @Test\n  public void testToLayers_writeToRoot() throws IOException, URISyntaxException {\n    // this test defines the current behavior of writing to root, perhaps we should ignore\n    // root at this level or we should ignore it at the builder level\n    Path testRoot = getLayersTestRoot(\"writeToRoot\");\n    List<FileEntriesLayer> layers = parseLayers(testRoot, 2);\n\n    checkLayer(\n        layers.get(0),\n        \"root writer\",\n        ImmutableSet.of(\n            newEntry(testRoot, \"dir\", \"/\", \"755\", 1000, \"\"),\n            newEntry(testRoot, \"dir/file.txt\", \"/file.txt\", \"644\", 1000, \"\")));\n\n    checkLayer(\n        layers.get(1),\n        \"root parent fill\",\n        ImmutableSet.of(\n            newEntry(testRoot, \".\", \"/\", \"755\", 1000, \"\"),\n            newEntry(testRoot, \"./dir\", \"/dir\", \"755\", 1000, \"\"),\n            newEntry(testRoot, \"./dir/file.txt\", \"/dir/file.txt\", \"644\", 1000, \"\")));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/PlatformSpecTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link PlatformSpec}. */\npublic class PlatformSpecTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());\n\n  @Test\n  public void testPlatformSpec_full() throws JsonProcessingException {\n    String data = \"architecture: amd64\\n\" + \"os: linux\\n\";\n\n    PlatformSpec parsed = mapper.readValue(data, PlatformSpec.class);\n    Assert.assertEquals(\"amd64\", parsed.getArchitecture());\n    Assert.assertEquals(\"linux\", parsed.getOs());\n  }\n\n  @Test\n  public void testPlatformSpec_osRequired() {\n    String data = \"architecture: amd64\\n\";\n\n    try {\n      mapper.readValue(data, PlatformSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.startsWith(\"Missing required creator property 'os'\"));\n    }\n  }\n\n  @Test\n  public void testPlatformSpec_osNotNull() {\n    String data = \"architecture: amd64\\n\" + \"os: null\";\n\n    try {\n      mapper.readValue(data, PlatformSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'os' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testPlatformSpec_osNotEmpty() {\n    String data = \"architecture: amd64\\n\" + \"os: ''\";\n\n    try {\n      mapper.readValue(data, PlatformSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'os' cannot be an empty string\"));\n    }\n  }\n\n  @Test\n  public void testPlatformSpec_architectureRequired() {\n    String data = \"os: linux\\n\";\n\n    try {\n      mapper.readValue(data, PlatformSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.startsWith(\"Missing required creator property 'architecture'\"));\n    }\n  }\n\n  @Test\n  public void testPlatformSpec_architectureNotNull() {\n    String data = \"architecture: null\\n\" + \"os: linux\";\n\n    try {\n      mapper.readValue(data, PlatformSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(), CoreMatchers.containsString(\"Property 'architecture' cannot be null\"));\n    }\n  }\n\n  @Test\n  public void testPlatformSpec_architectureNotEmpty() {\n    String data = \"architecture: ''\\n\" + \"os: linux\";\n\n    try {\n      mapper.readValue(data, PlatformSpec.class);\n      Assert.fail();\n    } catch (JsonProcessingException jpe) {\n      MatcherAssert.assertThat(\n          jpe.getMessage(),\n          CoreMatchers.containsString(\"Property 'architecture' cannot be an empty string\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/ValidatorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.buildfile;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Validator}. */\npublic class ValidatorTest {\n\n  @Test\n  public void testCheckNotNullAndNotEmpty_stringPass() {\n    Validator.checkNotNullAndNotEmpty(\"value\", \"ignored\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNotNullAndNotEmpty_stringFailNull() {\n    try {\n      Validator.checkNotNullAndNotEmpty((String) null, \"test\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot be null\", npe.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNotNullAndNotEmpty_stringFailEmpty() {\n    try {\n      Validator.checkNotNullAndNotEmpty(\"  \", \"test\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\"Property 'test' cannot be an empty string\", iae.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNotEmpty_valuePass() {\n    Validator.checkNullOrNotEmpty(\"value\", \"test\");\n  }\n\n  @Test\n  public void testCheckNullOrNotEmpty_nullPass() {\n    Validator.checkNullOrNotEmpty(null, \"test\");\n  }\n\n  @Test\n  public void testCheckNullOrNotEmpty_fail() {\n    try {\n      Validator.checkNullOrNotEmpty(\"   \", \"test\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\"Property 'test' cannot be an empty string\", iae.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNotEmpty_collectionPass() {\n    Validator.checkNotNullAndNotEmpty(ImmutableList.of(\"value\"), \"ignored\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNotEmpty_collectionFailNull() {\n    try {\n      Validator.checkNotNullAndNotEmpty((Collection<?>) null, \"test\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot be null\", npe.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNotEmpty_collectionFailEmpty() {\n    try {\n      Validator.checkNotNullAndNotEmpty(ImmutableList.of(), \"test\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\"Property 'test' cannot be an empty collection\", iae.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_nullMapPass() {\n    Validator.checkNullOrNonNullNonEmptyEntries((Map<String, String>) null, \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_emptyMapPass() {\n    Validator.checkNullOrNonNullNonEmptyEntries(ImmutableMap.of(), \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_mapWithValuesPass() {\n    Validator.checkNullOrNonNullNonEmptyEntries(\n        ImmutableMap.of(\"key1\", \"val1\", \"key2\", \"val2\"), \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_mapNullKeyFail() {\n    try {\n      Validator.checkNullOrNonNullNonEmptyEntries(Collections.singletonMap(null, \"val1\"), \"test\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot contain null keys\", npe.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_mapEmptyKeyFail() {\n    try {\n      Validator.checkNullOrNonNullNonEmptyEntries(Collections.singletonMap(\" \", \"val1\"), \"test\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\"Property 'test' cannot contain empty string keys\", iae.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_mapNullValueFail() {\n    try {\n      Validator.checkNullOrNonNullNonEmptyEntries(Collections.singletonMap(\"key1\", null), \"test\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot contain null values\", npe.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_mapEmptyValueFail() {\n    try {\n      Validator.checkNullOrNonNullNonEmptyEntries(Collections.singletonMap(\"key1\", \" \"), \"test\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\"Property 'test' cannot contain empty string values\", iae.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_nullPass() {\n    Validator.checkNullOrNonNullNonEmptyEntries((List<String>) null, \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_emptyPass() {\n    Validator.checkNullOrNonNullNonEmptyEntries(ImmutableList.of(), \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullNonNullNonEmptyEntries_valuesPass() {\n    Validator.checkNullOrNonNullNonEmptyEntries(ImmutableList.of(\"first\", \"second\"), \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullNonNullNonEmptyEntries_nullValueFail() {\n    try {\n      Validator.checkNullOrNonNullNonEmptyEntries(Arrays.asList(\"first\", null), \"test\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot contain null entries\", npe.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullNonEmptyEntries_emptyValueFail() {\n    try {\n      Validator.checkNullOrNonNullNonEmptyEntries(ImmutableList.of(\"first\", \"  \"), \"test\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\"Property 'test' cannot contain empty strings\", iae.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckNullOrNonNullEntries_nullPass() {\n    Validator.checkNullOrNonNullEntries(null, \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullEntries_emptyPass() {\n    Validator.checkNullOrNonNullEntries(ImmutableList.of(), \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullEntries_valuesPass() {\n    Validator.checkNullOrNonNullEntries(ImmutableList.of(new Object(), new Object()), \"test\");\n    // pass\n  }\n\n  @Test\n  public void testCheckNullOrNonNullEntries_nullFail() {\n    try {\n      Validator.checkNullOrNonNullEntries(Arrays.asList(new Object(), null), \"test\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot contain null entries\", npe.getMessage());\n    }\n    // pass\n  }\n\n  @Test\n  public void testCheckEquals_pass() {\n    Validator.checkEquals(\"value\", \"ignored\", \"value\");\n    // pass\n  }\n\n  @Test\n  public void testCheckEquals_failsNull() {\n    try {\n      Validator.checkEquals(null, \"test\", \"something\");\n      Assert.fail();\n    } catch (NullPointerException npe) {\n      Assert.assertEquals(\"Property 'test' cannot be null\", npe.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckEquals_failsNotEquals() {\n    try {\n      Validator.checkEquals(\"somethingElse\", \"test\", \"something\");\n      Assert.fail();\n    } catch (IllegalArgumentException iae) {\n      Assert.assertEquals(\n          \"Property 'test' must be 'something' but is 'somethingElse'\", iae.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/JarFilesTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.cli.CommonCliOptions;\nimport com.google.cloud.tools.jib.cli.CommonContainerConfigCliOptions;\nimport com.google.cloud.tools.jib.cli.Jar;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Optional;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\n\n/** Tests for {@link JarFiles}. */\n@RunWith(JUnitParamsRunner.class)\npublic class JarFilesTest {\n\n  @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule().silent();\n\n  @Mock private StandardExplodedProcessor mockStandardExplodedProcessor;\n  @Mock private StandardPackagedProcessor mockStandardPackagedProcessor;\n  @Mock private SpringBootExplodedProcessor mockSpringBootExplodedProcessor;\n  @Mock private SpringBootPackagedProcessor mockSpringBootPackagedProcessor;\n  @Mock private Jar mockJarCommand;\n  @Mock private CommonCliOptions mockCommonCliOptions;\n  @Mock private CommonContainerConfigCliOptions mockCommonContainerConfigCliOptions;\n  @Mock private ConsoleLogger mockLogger;\n\n  @Test\n  @Parameters(\n      value = {\n        \"8, eclipse-temurin:8-jre\",\n        \"9, eclipse-temurin:11-jre\",\n        \"11, eclipse-temurin:11-jre\",\n        \"13, eclipse-temurin:17-jre\",\n        \"17, eclipse-temurin:17-jre\",\n        \"21, eclipse-temurin:21-jre\",\n        \"25, eclipse-temurin:25-jre\",\n      })\n  public void testToJibContainer_defaultBaseImage(int javaVersion, String expectedBaseImage)\n      throws IOException, InvalidImageReferenceException {\n    when(mockStandardExplodedProcessor.getJavaVersion()).thenReturn(javaVersion);\n    JibContainerBuilder containerBuilder =\n        JarFiles.toJibContainerBuilder(\n            mockStandardExplodedProcessor,\n            mockJarCommand,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(expectedBaseImage);\n  }\n\n  @Test\n  public void testToJibContainerBuilder_explodedStandard_basicInfo()\n      throws IOException, InvalidImageReferenceException {\n    when(mockStandardExplodedProcessor.getJavaVersion()).thenReturn(8);\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .setName(\"classes\")\n            .addEntry(\n                Paths.get(\"path/to/tempDirectory/class1.class\"),\n                AbsoluteUnixPath.get(\"/app/explodedJar/class1.class\"))\n            .build();\n    when(mockStandardExplodedProcessor.createLayers()).thenReturn(Arrays.asList(layer));\n    when(mockStandardExplodedProcessor.computeEntrypoint(anyList()))\n        .thenReturn(\n            ImmutableList.of(\"java\", \"-cp\", \"/app/explodedJar:/app/dependencies/*\", \"HelloWorld\"));\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.empty());\n\n    JibContainerBuilder containerBuilder =\n        JarFiles.toJibContainerBuilder(\n            mockStandardExplodedProcessor,\n            mockJarCommand,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"eclipse-temurin:8-jre\");\n    assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n    assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH);\n    assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker);\n    assertThat(buildPlan.getEnvironment()).isEmpty();\n    assertThat(buildPlan.getLabels()).isEmpty();\n    assertThat(buildPlan.getVolumes()).isEmpty();\n    assertThat(buildPlan.getExposedPorts()).isEmpty();\n    assertThat(buildPlan.getUser()).isNull();\n    assertThat(buildPlan.getWorkingDirectory()).isNull();\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-cp\", \"/app/explodedJar:/app/dependencies/*\", \"HelloWorld\")\n        .inOrder();\n    assertThat(buildPlan.getLayers()).hasSize(1);\n    assertThat(buildPlan.getLayers().get(0).getName()).isEqualTo(\"classes\");\n    assertThat(((FileEntriesLayer) buildPlan.getLayers().get(0)).getEntries())\n        .containsExactlyElementsIn(\n            FileEntriesLayer.builder()\n                .addEntry(\n                    Paths.get(\"path/to/tempDirectory/class1.class\"),\n                    AbsoluteUnixPath.get(\"/app/explodedJar/class1.class\"))\n                .build()\n                .getEntries());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_packagedStandard_basicInfo()\n      throws IOException, InvalidImageReferenceException {\n    when(mockStandardPackagedProcessor.getJavaVersion()).thenReturn(8);\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .setName(\"jar\")\n            .addEntry(\n                Paths.get(\"path/to/standardJar.jar\"), AbsoluteUnixPath.get(\"/app/standardJar.jar\"))\n            .build();\n    when(mockStandardPackagedProcessor.createLayers()).thenReturn(Arrays.asList(layer));\n    when(mockStandardPackagedProcessor.computeEntrypoint(anyList()))\n        .thenReturn(ImmutableList.of(\"java\", \"-jar\", \"/app/standardJar.jar\"));\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.empty());\n\n    JibContainerBuilder containerBuilder =\n        JarFiles.toJibContainerBuilder(\n            mockStandardPackagedProcessor,\n            mockJarCommand,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"eclipse-temurin:8-jre\");\n    assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n    assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH);\n    assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker);\n    assertThat(buildPlan.getEnvironment()).isEmpty();\n    assertThat(buildPlan.getLabels()).isEmpty();\n    assertThat(buildPlan.getVolumes()).isEmpty();\n    assertThat(buildPlan.getExposedPorts()).isEmpty();\n    assertThat(buildPlan.getUser()).isNull();\n    assertThat(buildPlan.getWorkingDirectory()).isNull();\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-jar\", \"/app/standardJar.jar\")\n        .inOrder();\n    assertThat(buildPlan.getLayers().get(0).getName()).isEqualTo(\"jar\");\n    assertThat(((FileEntriesLayer) buildPlan.getLayers().get(0)).getEntries())\n        .isEqualTo(\n            FileEntriesLayer.builder()\n                .addEntry(\n                    Paths.get(\"path/to/standardJar.jar\"),\n                    AbsoluteUnixPath.get(\"/app/standardJar.jar\"))\n                .build()\n                .getEntries());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_explodedLayeredSpringBoot_basicInfo()\n      throws IOException, InvalidImageReferenceException {\n    when(mockSpringBootExplodedProcessor.getJavaVersion()).thenReturn(8);\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .setName(\"classes\")\n            .addEntry(\n                Paths.get(\"path/to/tempDirectory/BOOT-INF/classes/class1.class\"),\n                AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/class1.class\"))\n            .build();\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.empty());\n    when(mockSpringBootExplodedProcessor.createLayers()).thenReturn(Arrays.asList(layer));\n    when(mockSpringBootExplodedProcessor.computeEntrypoint(anyList()))\n        .thenReturn(\n            ImmutableList.of(\"java\", \"-cp\", \"/app\", \"org.springframework.boot.loader.JarLauncher\"));\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.empty());\n\n    JibContainerBuilder containerBuilder =\n        JarFiles.toJibContainerBuilder(\n            mockSpringBootExplodedProcessor,\n            mockJarCommand,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"eclipse-temurin:8-jre\");\n    assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n    assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH);\n    assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker);\n    assertThat(buildPlan.getEnvironment()).isEmpty();\n    assertThat(buildPlan.getLabels()).isEmpty();\n    assertThat(buildPlan.getVolumes()).isEmpty();\n    assertThat(buildPlan.getExposedPorts()).isEmpty();\n    assertThat(buildPlan.getUser()).isNull();\n    assertThat(buildPlan.getWorkingDirectory()).isNull();\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-cp\", \"/app\", \"org.springframework.boot.loader.JarLauncher\")\n        .inOrder();\n    assertThat(buildPlan.getLayers()).hasSize(1);\n    assertThat(buildPlan.getLayers().get(0).getName()).isEqualTo(\"classes\");\n    assertThat(((FileEntriesLayer) buildPlan.getLayers().get(0)).getEntries())\n        .containsExactlyElementsIn(\n            FileEntriesLayer.builder()\n                .addEntry(\n                    Paths.get(\"path/to/tempDirectory/BOOT-INF/classes/class1.class\"),\n                    AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/class1.class\"))\n                .build()\n                .getEntries());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_packagedSpringBoot_basicInfo()\n      throws IOException, InvalidImageReferenceException {\n    when(mockSpringBootPackagedProcessor.getJavaVersion()).thenReturn(8);\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .setName(\"jar\")\n            .addEntry(\n                Paths.get(\"path/to/spring-boot.jar\"), AbsoluteUnixPath.get(\"/app/spring-boot.jar\"))\n            .build();\n    when(mockSpringBootPackagedProcessor.createLayers()).thenReturn(Arrays.asList(layer));\n    when(mockSpringBootPackagedProcessor.computeEntrypoint(anyList()))\n        .thenReturn(ImmutableList.of(\"java\", \"-jar\", \"/app/spring-boot.jar\"));\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.empty());\n\n    JibContainerBuilder containerBuilder =\n        JarFiles.toJibContainerBuilder(\n            mockSpringBootPackagedProcessor,\n            mockJarCommand,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"eclipse-temurin:8-jre\");\n    assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n    assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH);\n    assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker);\n    assertThat(buildPlan.getEnvironment()).isEmpty();\n    assertThat(buildPlan.getLabels()).isEmpty();\n    assertThat(buildPlan.getVolumes()).isEmpty();\n    assertThat(buildPlan.getExposedPorts()).isEmpty();\n    assertThat(buildPlan.getUser()).isNull();\n    assertThat(buildPlan.getWorkingDirectory()).isNull();\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-jar\", \"/app/spring-boot.jar\")\n        .inOrder();\n    assertThat(buildPlan.getLayers()).hasSize(1);\n    assertThat(buildPlan.getLayers().get(0).getName()).isEqualTo(\"jar\");\n    assertThat(((FileEntriesLayer) buildPlan.getLayers().get(0)).getEntries())\n        .isEqualTo(\n            FileEntriesLayer.builder()\n                .addEntry(\n                    Paths.get(\"path/to/spring-boot.jar\"),\n                    AbsoluteUnixPath.get(\"/app/spring-boot.jar\"))\n                .build()\n                .getEntries());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_optionalParameters()\n      throws IOException, InvalidImageReferenceException {\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.of(\"base-image\"));\n    when(mockCommonContainerConfigCliOptions.getExposedPorts())\n        .thenReturn(ImmutableSet.of(Port.udp(123)));\n    when(mockCommonContainerConfigCliOptions.getVolumes())\n        .thenReturn(\n            ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")));\n    when(mockCommonContainerConfigCliOptions.getEnvironment())\n        .thenReturn(ImmutableMap.of(\"key1\", \"value1\"));\n    when(mockCommonContainerConfigCliOptions.getLabels())\n        .thenReturn(ImmutableMap.of(\"label\", \"mylabel\"));\n    when(mockCommonContainerConfigCliOptions.getUser()).thenReturn(Optional.of(\"customUser\"));\n    when(mockCommonContainerConfigCliOptions.getFormat()).thenReturn(Optional.of(ImageFormat.OCI));\n    when(mockCommonContainerConfigCliOptions.getProgramArguments())\n        .thenReturn(ImmutableList.of(\"arg1\"));\n    when(mockCommonContainerConfigCliOptions.getEntrypoint())\n        .thenReturn(ImmutableList.of(\"custom\", \"entrypoint\"));\n    when(mockCommonContainerConfigCliOptions.getCreationTime())\n        .thenReturn(Optional.of(Instant.ofEpochSecond(5)));\n\n    JibContainerBuilder containerBuilder =\n        JarFiles.toJibContainerBuilder(\n            mockStandardExplodedProcessor,\n            mockJarCommand,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"base-image\");\n    assertThat(buildPlan.getExposedPorts()).isEqualTo(ImmutableSet.of(Port.udp(123)));\n    assertThat(buildPlan.getVolumes())\n        .isEqualTo(\n            ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")));\n    assertThat(buildPlan.getEnvironment()).isEqualTo(ImmutableMap.of(\"key1\", \"value1\"));\n    assertThat(buildPlan.getLabels()).isEqualTo(ImmutableMap.of(\"label\", \"mylabel\"));\n    assertThat(buildPlan.getUser()).isEqualTo(\"customUser\");\n    assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.OCI);\n    assertThat(buildPlan.getCmd()).isEqualTo(ImmutableList.of(\"arg1\"));\n    assertThat(buildPlan.getEntrypoint()).isEqualTo(ImmutableList.of(\"custom\", \"entrypoint\"));\n    assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.ofEpochSecond(5));\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/SpringBootExplodedProcessorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link SpringBootExplodedProcessor}. */\npublic class SpringBootExplodedProcessorTest {\n\n  private static final String SPRING_BOOT_LAYERED = \"jar/spring-boot/springboot_layered.jar\";\n  private static final String SPRING_BOOT_LAYERED_WITH_EMPTY_LAYER =\n      \"jar/spring-boot/springboot_layered_singleEmptyLayer.jar\";\n  private static final String SPRING_BOOT_LAYERED_WITH_ALL_EMPTY_LAYERS_LISTED =\n      \"jar/spring-boot/springboot_layered_allEmptyLayers.jar\";\n  private static final String SPRING_BOOT_NOT_LAYERED = \"jar/spring-boot/springboot_notLayered.jar\";\n  private static final Integer JAR_JAVA_VERSION = 0; // any value\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testCreateLayers_layered_allListed() throws IOException, URISyntaxException {\n    // BOOT-INF/layers.idx for this spring-boot jar is as follows:\n    // - \"dependencies\":\n    //   - \"BOOT-INF/lib/dependency1.jar\"\n    //   - \"BOOT-INF/lib/dependency2.jar\"\n    // - \"spring-boot-loader\":\n    //   - \"org/\"\n    // - \"snapshot-dependencies\":\n    //   - \"BOOT-INF/lib/dependency-SNAPSHOT-3.jar\"\n    // - \"application\":\n    //   - \"BOOT-INF/classes/\"\n    //   - \"META-INF/\"\n    Path springBootJar = Paths.get(Resources.getResource(SPRING_BOOT_LAYERED).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    SpringBootExplodedProcessor springBootExplodedModeProcessor =\n        new SpringBootExplodedProcessor(springBootJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = springBootExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(4);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer loaderLayer = layers.get(1);\n    FileEntriesLayer snapshotLayer = layers.get(2);\n    FileEntriesLayer applicationLayer = layers.get(3);\n\n    assertThat(nonSnapshotLayer.getName()).isEqualTo(\"dependencies\");\n    assertThat(\n            nonSnapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency1.jar\"),\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency2.jar\"));\n\n    assertThat(loaderLayer.getName()).isEqualTo(\"spring-boot-loader\");\n    assertThat(\n            loaderLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/org/springframework/boot/loader/data/data1.class\"),\n            AbsoluteUnixPath.get(\"/app/org/springframework/boot/loader/launcher1.class\"));\n\n    assertThat(snapshotLayer.getName()).isEqualTo(\"snapshot-dependencies\");\n    assertThat(\n            snapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency3-SNAPSHOT.jar\"));\n\n    assertThat(applicationLayer.getName()).isEqualTo(\"application\");\n    assertThat(\n            applicationLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/class1.class\"),\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/classDirectory/class2.class\"),\n            AbsoluteUnixPath.get(\"/app/META-INF/MANIFEST.MF\"));\n  }\n\n  @Test\n  public void testCreateLayers_layered_singleEmptyLayerListed()\n      throws IOException, URISyntaxException {\n    // BOOT-INF/layers.idx for this spring-boot jar is as follows:\n    // - \"dependencies\":\n    //   - \"BOOT-INF/lib/dependency1.jar\"\n    //   - \"BOOT-INF/lib/dependency2.jar\"\n    // - \"spring-boot-loader\":\n    //   - \"org/\"\n    // - \"snapshot-dependencies\":\n    // - \"application\":\n    //   - \"BOOT-INF/classes/\"\n    //   - \"META-INF/\"\n    Path springBootJar =\n        Paths.get(Resources.getResource(SPRING_BOOT_LAYERED_WITH_EMPTY_LAYER).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    SpringBootExplodedProcessor springBootExplodedModeProcessor =\n        new SpringBootExplodedProcessor(springBootJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = springBootExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(3);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer loaderLayer = layers.get(1);\n    FileEntriesLayer applicationLayer = layers.get(2);\n\n    assertThat(nonSnapshotLayer.getName()).isEqualTo(\"dependencies\");\n    assertThat(\n            nonSnapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency1.jar\"),\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency2.jar\"));\n\n    assertThat(loaderLayer.getName()).isEqualTo(\"spring-boot-loader\");\n    assertThat(\n            loaderLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/org/springframework/boot/loader/data/data1.class\"),\n            AbsoluteUnixPath.get(\"/app/org/springframework/boot/loader/launcher1.class\"));\n\n    assertThat(applicationLayer.getName()).isEqualTo(\"application\");\n    assertThat(\n            applicationLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/class1.class\"),\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/classDirectory/class2.class\"),\n            AbsoluteUnixPath.get(\"/app/META-INF/MANIFEST.MF\"));\n  }\n\n  @Test\n  public void testCreateLayers_layered_allEmptyLayersListed()\n      throws IOException, URISyntaxException {\n    // BOOT-INF/layers.idx for this spring-boot jar is as follows:\n    // - \"dependencies\":\n    // - \"spring-boot-loader\":\n    // - \"snapshot-dependencies\":\n    // - \"application\":\n    Path springBootJar =\n        Paths.get(Resources.getResource(SPRING_BOOT_LAYERED_WITH_ALL_EMPTY_LAYERS_LISTED).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    SpringBootExplodedProcessor springBootExplodedModeProcessor =\n        new SpringBootExplodedProcessor(springBootJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = springBootExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(0);\n  }\n\n  @Test\n  public void testCreateLayers_nonLayered() throws IOException, URISyntaxException {\n    Path springBootJar = Paths.get(Resources.getResource(SPRING_BOOT_NOT_LAYERED).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    SpringBootExplodedProcessor springBootExplodedModeProcessor =\n        new SpringBootExplodedProcessor(springBootJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = springBootExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(5);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer loaderLayer = layers.get(1);\n    FileEntriesLayer snapshotLayer = layers.get(2);\n    FileEntriesLayer resourcesLayer = layers.get(3);\n    FileEntriesLayer classesLayer = layers.get(4);\n\n    assertThat(nonSnapshotLayer.getName()).isEqualTo(\"dependencies\");\n    assertThat(\n            nonSnapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency1.jar\"),\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency2.jar\"));\n\n    assertThat(loaderLayer.getName()).isEqualTo(\"spring-boot-loader\");\n    assertThat(\n            loaderLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/org/springframework/boot/loader/data/data1.class\"),\n            AbsoluteUnixPath.get(\"/app/org/springframework/boot/loader/launcher1.class\"));\n\n    assertThat(snapshotLayer.getName()).isEqualTo(\"snapshot dependencies\");\n    assertThat(snapshotLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/BOOT-INF/lib/dependency3-SNAPSHOT.jar\"));\n\n    assertThat(resourcesLayer.getName()).isEqualTo(\"resources\");\n    assertThat(resourcesLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/META-INF/MANIFEST.MF\"));\n\n    assertThat(classesLayer.getName()).isEqualTo(\"classes\");\n    assertThat(\n            classesLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/class1.class\"),\n            AbsoluteUnixPath.get(\"/app/BOOT-INF/classes/classDirectory/class2.class\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint() {\n    SpringBootExplodedProcessor bootProcessor =\n        new SpringBootExplodedProcessor(\n            Paths.get(\"ignored\"), Paths.get(\"ignored\"), JAR_JAVA_VERSION);\n    ImmutableList<String> actualEntrypoint = bootProcessor.computeEntrypoint(new ArrayList<>());\n    assertThat(actualEntrypoint)\n        .isEqualTo(\n            ImmutableList.of(\"java\", \"-cp\", \"/app\", \"org.springframework.boot.loader.JarLauncher\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint_withJvmFlags() {\n    SpringBootExplodedProcessor bootProcessor =\n        new SpringBootExplodedProcessor(\n            Paths.get(\"ignored\"), Paths.get(\"ignored\"), JAR_JAVA_VERSION);\n    ImmutableList<String> actualEntrypoint =\n        bootProcessor.computeEntrypoint(ImmutableList.of(\"-jvm-flag\"));\n    assertThat(actualEntrypoint)\n        .isEqualTo(\n            ImmutableList.of(\n                \"java\", \"-jvm-flag\", \"-cp\", \"/app\", \"org.springframework.boot.loader.JarLauncher\"));\n  }\n\n  @Test\n  public void testGetJavaVersion() {\n    SpringBootExplodedProcessor springBootExplodedProcessor =\n        new SpringBootExplodedProcessor(Paths.get(\"ignore\"), Paths.get(\"ignore\"), 8);\n    assertThat(springBootExplodedProcessor.getJavaVersion()).isEqualTo(8);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/SpringBootPackagedProcessorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport org.junit.Test;\n\n/** Tests for {@link SpringBootPackagedProcessor}. */\npublic class SpringBootPackagedProcessorTest {\n\n  private static final String SPRING_BOOT_JAR = \"jar/spring-boot/springboot_sample.jar\";\n  private static final Integer JAR_JAVA_VERSION = 0; // any value\n\n  @Test\n  public void testCreateLayers() throws URISyntaxException {\n    Path springBootJar = Paths.get(Resources.getResource(SPRING_BOOT_JAR).toURI());\n    SpringBootPackagedProcessor springBootProcessor =\n        new SpringBootPackagedProcessor(springBootJar, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = springBootProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(1);\n\n    FileEntriesLayer jarLayer = layers.get(0);\n\n    assertThat(jarLayer.getName()).isEqualTo(\"jar\");\n    assertThat(jarLayer.getEntries().size()).isEqualTo(1);\n    assertThat(jarLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/springboot_sample.jar\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint() throws URISyntaxException {\n    Path springBootJar = Paths.get(Resources.getResource(SPRING_BOOT_JAR).toURI());\n    SpringBootPackagedProcessor springBootProcessor =\n        new SpringBootPackagedProcessor(springBootJar, JAR_JAVA_VERSION);\n\n    ImmutableList<String> actualEntrypoint =\n        springBootProcessor.computeEntrypoint(ImmutableList.of());\n\n    assertThat(actualEntrypoint)\n        .isEqualTo(ImmutableList.of(\"java\", \"-jar\", \"/app/springboot_sample.jar\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint_jvmFlag() throws URISyntaxException {\n    Path springBootJar = Paths.get(Resources.getResource(SPRING_BOOT_JAR).toURI());\n    SpringBootPackagedProcessor springBootProcessor =\n        new SpringBootPackagedProcessor(springBootJar, JAR_JAVA_VERSION);\n\n    ImmutableList<String> actualEntrypoint =\n        springBootProcessor.computeEntrypoint(ImmutableList.of(\"-jvm-flag\"));\n\n    assertThat(actualEntrypoint)\n        .isEqualTo(ImmutableList.of(\"java\", \"-jvm-flag\", \"-jar\", \"/app/springboot_sample.jar\"));\n  }\n\n  @Test\n  public void testGetJavaVersion() {\n    SpringBootPackagedProcessor springBootPackagedProcessor =\n        new SpringBootPackagedProcessor(Paths.get(\"ignore\"), 8);\n    assertThat(springBootPackagedProcessor.getJavaVersion()).isEqualTo(8);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/StandardExplodedProcessorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link StandardExplodedProcessor}. */\npublic class StandardExplodedProcessorTest {\n\n  private static final String STANDARD_JAR_WITHOUT_CLASS_PATH_MANIFEST =\n      \"jar/standard/standardJarWithoutClassPath.jar\";\n  private static final String STANDARD_JAR_WITH_CLASS_PATH_MANIFEST =\n      \"jar/standard/standardJarWithClassPath.jar\";\n  private static final String STANDARD_JAR_WITH_ONLY_CLASSES =\n      \"jar/standard/standardJarWithOnlyClasses.jar\";\n  private static final String STANDARD_JAR_EMPTY = \"jar/standard/emptyStandardJar.jar\";\n  private static final String STANDARD_SINGLE_DEPENDENCY_JAR = \"jar/standard/singleDepJar.jar\";\n  private static final Integer JAR_JAVA_VERSION = 0; // any value\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testCreateLayers_emptyJar() throws IOException, URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_JAR_EMPTY).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = standardExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(1);\n\n    FileEntriesLayer resourcesLayer = layers.get(0);\n\n    assertThat(resourcesLayer.getEntries().size()).isEqualTo(1);\n    assertThat(resourcesLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/explodedJar/META-INF/MANIFEST.MF\"));\n  }\n\n  @Test\n  public void testCreateLayers_withClassPathInManifest() throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = standardExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(4);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer snapshotLayer = layers.get(1);\n    FileEntriesLayer resourcesLayer = layers.get(2);\n    FileEntriesLayer classesLayer = layers.get(3);\n\n    // Validate dependencies layers.\n    assertThat(nonSnapshotLayer.getName()).isEqualTo(\"dependencies\");\n    assertThat(\n            nonSnapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .isEqualTo(\n            ImmutableList.of(\n                AbsoluteUnixPath.get(\"/app/dependencies/dependency1\"),\n                AbsoluteUnixPath.get(\"/app/dependencies/dependency2\"),\n                AbsoluteUnixPath.get(\"/app/dependencies/dependency4\")));\n    assertThat(snapshotLayer.getName()).isEqualTo(\"snapshot dependencies\");\n    assertThat(snapshotLayer.getEntries().size()).isEqualTo(1);\n    assertThat(snapshotLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/dependencies/dependency3-SNAPSHOT-1.jar\"));\n\n    // Validate resources layer.\n    assertThat(resourcesLayer.getName()).isEqualTo(\"resources\");\n    List<AbsoluteUnixPath> actualResourcesPaths =\n        resourcesLayer.getEntries().stream()\n            .map(FileEntry::getExtractionPath)\n            .collect(Collectors.toList());\n    assertThat(actualResourcesPaths)\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/explodedJar/META-INF/MANIFEST.MF\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory1/resource1.txt\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory2/directory3/resource2.sql\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory4/resource3.txt\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/resource4.sql\"));\n\n    // Validate classes layer.\n    assertThat(classesLayer.getName()).isEqualTo(\"classes\");\n    List<AbsoluteUnixPath> actualClassesPaths =\n        classesLayer.getEntries().stream()\n            .map(FileEntry::getExtractionPath)\n            .collect(Collectors.toList());\n    assertThat(actualClassesPaths)\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/explodedJar/class5.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory1/class1.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory1/class2.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory2/class4.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory2/directory3/class3.class\"));\n  }\n\n  @Test\n  public void testCreateLayers_withoutClassPathInManifest() throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITHOUT_CLASS_PATH_MANIFEST).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = standardExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(2);\n\n    FileEntriesLayer resourcesLayer = layers.get(0);\n    FileEntriesLayer classesLayer = layers.get(1);\n\n    // Validate resources layer.\n    assertThat(resourcesLayer.getName()).isEqualTo(\"resources\");\n    List<AbsoluteUnixPath> actualResourcesPaths =\n        resourcesLayer.getEntries().stream()\n            .map(FileEntry::getExtractionPath)\n            .collect(Collectors.toList());\n    assertThat(actualResourcesPaths)\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/explodedJar/META-INF/MANIFEST.MF\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory1/resource1.txt\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory2/directory3/resource2.sql\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory4/resource3.txt\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/resource4.sql\"));\n\n    // Validate classes layer.\n    assertThat(classesLayer.getName()).isEqualTo(\"classes\");\n    List<AbsoluteUnixPath> actualClassesPaths =\n        classesLayer.getEntries().stream()\n            .map(FileEntry::getExtractionPath)\n            .collect(Collectors.toList());\n    assertThat(actualClassesPaths)\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/explodedJar/class5.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory1/class1.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory1/class2.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory2/class4.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/directory2/directory3/class3.class\"));\n  }\n\n  @Test\n  public void testCreateLayers_withoutClassPathInManifest_containsOnlyClasses()\n      throws IOException, URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_JAR_WITH_ONLY_CLASSES).toURI());\n    Path destDir = temporaryFolder.newFolder().toPath();\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, destDir, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = standardExplodedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(2);\n\n    FileEntriesLayer resourcesLayer = layers.get(0);\n    FileEntriesLayer classesLayer = layers.get(1);\n\n    // Validate resources layer.\n    assertThat(resourcesLayer.getEntries().size()).isEqualTo(1);\n    assertThat(resourcesLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/explodedJar/META-INF/MANIFEST.MF\"));\n\n    // Validate classes layer.\n    List<AbsoluteUnixPath> actualClassesPath =\n        classesLayer.getEntries().stream()\n            .map(FileEntry::getExtractionPath)\n            .collect(Collectors.toList());\n    assertThat(actualClassesPath)\n        .containsExactly(\n            AbsoluteUnixPath.get(\"/app/explodedJar/class1.class\"),\n            AbsoluteUnixPath.get(\"/app/explodedJar/class2.class\"));\n  }\n\n  @Test\n  public void testCreateLayers_dependencyDoesNotExist() throws URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_SINGLE_DEPENDENCY_JAR).toURI());\n    Path destDir = temporaryFolder.getRoot().toPath();\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, destDir, JAR_JAVA_VERSION);\n\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class, () -> standardExplodedModeProcessor.createLayers());\n\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"Dependency required by the JAR (as specified in `Class-Path` in the JAR manifest) doesn't exist: \"\n                + standardJar.getParent().resolve(\"dependency.jar\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint_noMainClass() throws URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_JAR_EMPTY).toURI());\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, Paths.get(\"ignore\"), JAR_JAVA_VERSION);\n\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> standardExplodedModeProcessor.computeEntrypoint(ImmutableList.of()));\n\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"`Main-Class:` attribute for an application main class not defined in the input JAR's manifest \"\n                + \"(`META-INF/MANIFEST.MF` in the JAR).\");\n  }\n\n  @Test\n  public void testComputeEntrypoint_withMainClass() throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, Paths.get(\"ignore\"), JAR_JAVA_VERSION);\n\n    ImmutableList<String> actualEntrypoint =\n        standardExplodedModeProcessor.computeEntrypoint(ImmutableList.of());\n\n    assertThat(actualEntrypoint)\n        .isEqualTo(\n            ImmutableList.of(\"java\", \"-cp\", \"/app/explodedJar:/app/dependencies/*\", \"HelloWorld\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint_withMainClass_jvmFlags()\n      throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());\n    StandardExplodedProcessor standardExplodedModeProcessor =\n        new StandardExplodedProcessor(standardJar, Paths.get(\"ignore\"), JAR_JAVA_VERSION);\n\n    ImmutableList<String> actualEntrypoint =\n        standardExplodedModeProcessor.computeEntrypoint(ImmutableList.of(\"-jvm-flag\"));\n\n    assertThat(actualEntrypoint)\n        .isEqualTo(\n            ImmutableList.of(\n                \"java\", \"-jvm-flag\", \"-cp\", \"/app/explodedJar:/app/dependencies/*\", \"HelloWorld\"));\n  }\n\n  @Test\n  public void testGetJavaVersion() {\n    StandardExplodedProcessor standardExplodedProcessor =\n        new StandardExplodedProcessor(Paths.get(\"ignore\"), Paths.get(\"ignore\"), 8);\n    assertThat(standardExplodedProcessor.getJavaVersion()).isEqualTo(8);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/StandardPackagedProcessorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.jar;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.junit.Test;\n\n/** Tests for {@link StandardPackagedProcessor}. */\npublic class StandardPackagedProcessorTest {\n\n  private static final String STANDARD_JAR_EMPTY = \"jar/standard/emptyStandardJar.jar\";\n  private static final String STANDARD_SINGLE_DEPENDENCY_JAR = \"jar/standard/singleDepJar.jar\";\n  private static final String STANDARD_JAR_WITH_CLASS_PATH_MANIFEST =\n      \"jar/standard/standardJarWithClassPath.jar\";\n  private static final Integer JAR_JAVA_VERSION = 0; // any value\n\n  @Test\n  public void testCreateLayers_emptyJar() throws IOException, URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_JAR_EMPTY).toURI());\n    StandardPackagedProcessor standardPackagedModeProcessor =\n        new StandardPackagedProcessor(standardJar, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = standardPackagedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(1);\n\n    FileEntriesLayer jarLayer = layers.get(0);\n    assertThat(jarLayer.getName()).isEqualTo(\"jar\");\n    assertThat(jarLayer.getEntries().size()).isEqualTo(1);\n    assertThat(jarLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/emptyStandardJar.jar\"));\n  }\n\n  @Test\n  public void testCreateLayers_withClassPathInManifest() throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());\n    StandardPackagedProcessor standardPackagedModeProcessor =\n        new StandardPackagedProcessor(standardJar, JAR_JAVA_VERSION);\n\n    List<FileEntriesLayer> layers = standardPackagedModeProcessor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(3);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer snapshotLayer = layers.get(1);\n    FileEntriesLayer jarLayer = layers.get(2);\n\n    // Validate dependencies layers.\n    assertThat(nonSnapshotLayer.getName()).isEqualTo(\"dependencies\");\n    assertThat(\n            nonSnapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .isEqualTo(\n            ImmutableList.of(\n                AbsoluteUnixPath.get(\"/app/dependency1\"),\n                AbsoluteUnixPath.get(\"/app/dependency2\"),\n                AbsoluteUnixPath.get(\"/app/directory/dependency4\")));\n    assertThat(snapshotLayer.getName()).isEqualTo(\"snapshot dependencies\");\n    assertThat(\n            snapshotLayer.getEntries().stream()\n                .map(FileEntry::getExtractionPath)\n                .collect(Collectors.toList()))\n        .isEqualTo(ImmutableList.of(AbsoluteUnixPath.get(\"/app/dependency3-SNAPSHOT-1.jar\")));\n\n    // Validate jar layer.\n    assertThat(jarLayer.getName()).isEqualTo(\"jar\");\n    assertThat(jarLayer.getEntries().size()).isEqualTo(1);\n    assertThat(jarLayer.getEntries().get(0).getExtractionPath())\n        .isEqualTo(AbsoluteUnixPath.get(\"/app/standardJarWithClassPath.jar\"));\n  }\n\n  @Test\n  public void testCreateLayers_dependencyDoesNotExist() throws URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_SINGLE_DEPENDENCY_JAR).toURI());\n    StandardPackagedProcessor standardPackagedModeProcessor =\n        new StandardPackagedProcessor(standardJar, JAR_JAVA_VERSION);\n\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class, () -> standardPackagedModeProcessor.createLayers());\n\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"Dependency required by the JAR (as specified in `Class-Path` in the JAR manifest) doesn't exist: \"\n                + standardJar.getParent().resolve(\"dependency.jar\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint_noMainClass() throws URISyntaxException {\n    Path standardJar = Paths.get(Resources.getResource(STANDARD_JAR_EMPTY).toURI());\n    StandardPackagedProcessor standardPackagedModeProcessor =\n        new StandardPackagedProcessor(standardJar, JAR_JAVA_VERSION);\n\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> standardPackagedModeProcessor.computeEntrypoint(ImmutableList.of()));\n\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\n            \"`Main-Class:` attribute for an application main class not defined in the input JAR's manifest \"\n                + \"(`META-INF/MANIFEST.MF` in the JAR).\");\n  }\n\n  @Test\n  public void testComputeEntrypoint_withMainClass() throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());\n    StandardPackagedProcessor standardPackagedModeProcessor =\n        new StandardPackagedProcessor(standardJar, JAR_JAVA_VERSION);\n\n    ImmutableList<String> actualEntrypoint =\n        standardPackagedModeProcessor.computeEntrypoint(ImmutableList.of());\n\n    assertThat(actualEntrypoint)\n        .isEqualTo(ImmutableList.of(\"java\", \"-jar\", \"/app/standardJarWithClassPath.jar\"));\n  }\n\n  @Test\n  public void testComputeEntrypoint_withMainClass_jvmFlags()\n      throws IOException, URISyntaxException {\n    Path standardJar =\n        Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());\n    StandardPackagedProcessor standardPackagedModeProcessor =\n        new StandardPackagedProcessor(standardJar, JAR_JAVA_VERSION);\n\n    ImmutableList<String> actualEntrypoint =\n        standardPackagedModeProcessor.computeEntrypoint(ImmutableList.of(\"-jvm-flag\"));\n\n    assertThat(actualEntrypoint)\n        .isEqualTo(\n            ImmutableList.of(\"java\", \"-jvm-flag\", \"-jar\", \"/app/standardJarWithClassPath.jar\"));\n  }\n\n  @Test\n  public void testGetJavaVersion() {\n    StandardPackagedProcessor standardPackagedProcessor =\n        new StandardPackagedProcessor(Paths.get(\"ignore\"), 8);\n    assertThat(standardPackagedProcessor.getJavaVersion()).isEqualTo(8);\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/logging/CliLoggerTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.logging;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport java.io.PrintWriter;\nimport java.time.Duration;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.EnvironmentVariables;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link CliLogger}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class CliLoggerTest {\n\n  @Rule\n  public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();\n\n  @Rule public final EnvironmentVariables environmentVariables = new EnvironmentVariables();\n\n  @Mock private PrintWriter mockOut;\n  @Mock private PrintWriter mockErr;\n\n  private void createLoggerAndSendMessages(Verbosity verbosity, ConsoleOutput consoleOutput) {\n    SingleThreadedExecutor executor = new SingleThreadedExecutor();\n    ConsoleLogger logger =\n        CliLogger.newLogger(\n            verbosity, HttpTraceLevel.off, consoleOutput, mockOut, mockErr, executor);\n\n    logger.log(Level.DEBUG, \"debug\");\n    logger.log(Level.INFO, \"info\");\n    logger.log(Level.LIFECYCLE, \"lifecycle\");\n    logger.log(Level.PROGRESS, \"progress\");\n    logger.log(Level.WARN, \"warn\");\n    logger.log(Level.ERROR, \"error\");\n\n    executor.shutDownAndAwaitTermination(Duration.ofSeconds(3));\n  }\n\n  @Test\n  public void testLog_quiet_plainConsole() {\n    createLoggerAndSendMessages(Verbosity.quiet, ConsoleOutput.plain);\n\n    Mockito.verifyNoInteractions(mockOut);\n    Mockito.verifyNoInteractions(mockErr);\n  }\n\n  @Test\n  public void testLog_error_plainConsole() {\n    createLoggerAndSendMessages(Verbosity.error, ConsoleOutput.plain);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verifyNoInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_warn_plainConsole() {\n    createLoggerAndSendMessages(Verbosity.warn, ConsoleOutput.plain);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_lifecycle_plainConsole() {\n    createLoggerAndSendMessages(Verbosity.lifecycle, ConsoleOutput.plain);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verify(mockOut).println(\"lifecycle\");\n    Mockito.verify(mockOut).println(\"progress\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_info_plainConsole() {\n    createLoggerAndSendMessages(Verbosity.info, ConsoleOutput.plain);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verify(mockOut).println(\"lifecycle\");\n    Mockito.verify(mockOut).println(\"progress\");\n    Mockito.verify(mockOut).println(\"info\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_debug_plainConsole() {\n    createLoggerAndSendMessages(Verbosity.debug, ConsoleOutput.plain);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verify(mockOut).println(\"lifecycle\");\n    Mockito.verify(mockOut).println(\"progress\");\n    Mockito.verify(mockOut).println(\"info\");\n    Mockito.verify(mockOut).println(\"debug\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_quiet_richConsole() {\n    createLoggerAndSendMessages(Verbosity.quiet, ConsoleOutput.rich);\n\n    Mockito.verifyNoInteractions(mockOut);\n    Mockito.verifyNoInteractions(mockErr);\n  }\n\n  @Test\n  public void testLog_error_richConsole() {\n    createLoggerAndSendMessages(Verbosity.error, ConsoleOutput.rich);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verifyNoInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_warn_richConsole() {\n    createLoggerAndSendMessages(Verbosity.warn, ConsoleOutput.rich);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_lifecycle_richConsole() {\n    createLoggerAndSendMessages(Verbosity.lifecycle, ConsoleOutput.rich);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verify(mockOut).println(\"lifecycle\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_info_richConsole() {\n    createLoggerAndSendMessages(Verbosity.info, ConsoleOutput.rich);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verify(mockOut).println(\"lifecycle\");\n    Mockito.verify(mockOut).println(\"info\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testLog_debug_richConsole() {\n    createLoggerAndSendMessages(Verbosity.debug, ConsoleOutput.rich);\n\n    Mockito.verify(mockErr).println(\"[ERROR] error\");\n    Mockito.verifyNoMoreInteractions(mockErr);\n    Mockito.verify(mockOut).println(\"[WARN] warn\");\n    Mockito.verify(mockOut).println(\"lifecycle\");\n    Mockito.verify(mockOut).println(\"info\");\n    Mockito.verify(mockOut).println(\"debug\");\n    Mockito.verifyNoMoreInteractions(mockOut);\n  }\n\n  @Test\n  public void testIsRichConsole_true() {\n    assertThat(CliLogger.isRichConsole(ConsoleOutput.rich, HttpTraceLevel.off)).isTrue();\n  }\n\n  @Test\n  public void testIsRichConsole_falseIfHttpTrace() {\n    assertThat(CliLogger.isRichConsole(ConsoleOutput.rich, HttpTraceLevel.config)).isFalse();\n  }\n\n  @Test\n  public void testIsRichConsole_false() {\n    assertThat(CliLogger.isRichConsole(ConsoleOutput.plain, HttpTraceLevel.off)).isFalse();\n  }\n\n  @Test\n  public void testIsRightConsole_autoWindowsTrue() {\n    System.setProperty(\"os.name\", \"windows\");\n    assertThat(CliLogger.isRichConsole(ConsoleOutput.auto, HttpTraceLevel.off)).isTrue();\n  }\n\n  @Test\n  public void testIsRightConsole_autoDumbTermFalse() {\n    environmentVariables.set(\"TERM\", \"dumb\");\n    assertThat(CliLogger.isRichConsole(ConsoleOutput.auto, HttpTraceLevel.off)).isFalse();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/war/StandardWarExplodedProcessorTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.war;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport com.google.common.truth.Correspondence;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.StringJoiner;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\npublic class StandardWarExplodedProcessorTest {\n\n  private static final AbsoluteUnixPath APP_ROOT = AbsoluteUnixPath.get(\"/my/app\");\n  private static final Correspondence<FileEntry, String> EXTRACTION_PATH_OF =\n      Correspondence.transforming(\n          entry -> entry.getExtractionPath().toString(), \"has extractionPath of\");\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testCreateLayers_allLayers_correctExtractionPaths()\n      throws IOException, URISyntaxException {\n    // Prepare war file for test\n    Path tempDirectory = temporaryFolder.getRoot().toPath();\n    Path warContents = Paths.get(Resources.getResource(\"war/standard/allLayers\").toURI());\n    Path standardWar = zipUpDirectory(warContents, tempDirectory.resolve(\"standardWar.war\"));\n\n    Path explodedWarDestination = temporaryFolder.newFolder(\"exploded-war\").toPath();\n    StandardWarExplodedProcessor processor =\n        new StandardWarExplodedProcessor(standardWar, explodedWarDestination, APP_ROOT);\n    List<FileEntriesLayer> layers = processor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(4);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer snapshotLayer = layers.get(1);\n    FileEntriesLayer resourcesLayer = layers.get(2);\n    FileEntriesLayer classesLayer = layers.get(3);\n\n    assertThat(nonSnapshotLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependency-1.0.0.jar\");\n    assertThat(snapshotLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(resourcesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/META-INF/context.xml\",\n            \"/my/app/Test.jsp\",\n            \"/my/app/WEB-INF/web.xml\",\n            \"/my/app/WEB-INF/classes/package/test.properties\");\n    assertThat(classesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/WEB-INF/classes/MyClass2.class\",\n            \"/my/app/WEB-INF/classes/package/MyClass.class\");\n  }\n\n  @Test\n  public void testCreateLayers_webInfLibDoesNotExist_correctExtractionPaths()\n      throws IOException, URISyntaxException {\n    // Prepare war file for test\n    Path tempDirectory = temporaryFolder.getRoot().toPath();\n    Path warContents = Paths.get(Resources.getResource(\"war/standard/noWebInfLib\").toURI());\n    Path standardWar = zipUpDirectory(warContents, tempDirectory.resolve(\"noDependenciesWar.war\"));\n\n    Path explodedWarDestination = temporaryFolder.newFolder(\"exploded-war\").toPath();\n    StandardWarExplodedProcessor processor =\n        new StandardWarExplodedProcessor(standardWar, explodedWarDestination, APP_ROOT);\n    List<FileEntriesLayer> layers = processor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(2);\n\n    FileEntriesLayer resourcesLayer = layers.get(0);\n    FileEntriesLayer classesLayer = layers.get(1);\n\n    assertThat(resourcesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/META-INF/context.xml\");\n    assertThat(classesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/WEB-INF/classes/MyClass2.class\",\n            \"/my/app/WEB-INF/classes/package/MyClass.class\");\n  }\n\n  @Test\n  public void testCreateLayers_webInfClassesDoesNotExist_correctExtractionPaths()\n      throws IOException, URISyntaxException {\n    // Prepare war file for test\n    Path tempDirectory = temporaryFolder.getRoot().toPath();\n    Path warContents = Paths.get(Resources.getResource(\"war/standard/noWebInfClasses\").toURI());\n    Path standardWar = zipUpDirectory(warContents, tempDirectory.resolve(\"noClassesWar.war\"));\n\n    Path explodedWarDestination = temporaryFolder.newFolder(\"exploded-war\").toPath();\n    StandardWarExplodedProcessor processor =\n        new StandardWarExplodedProcessor(standardWar, explodedWarDestination, APP_ROOT);\n    List<FileEntriesLayer> layers = processor.createLayers();\n\n    assertThat(layers.size()).isEqualTo(3);\n\n    FileEntriesLayer nonSnapshotLayer = layers.get(0);\n    FileEntriesLayer snapshotLayer = layers.get(1);\n    FileEntriesLayer resourcesLayer = layers.get(2);\n\n    assertThat(nonSnapshotLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependency-1.0.0.jar\");\n    assertThat(snapshotLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(resourcesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/META-INF/context.xml\");\n  }\n\n  @Test\n  public void testComputeEntrypoint() {\n    StandardWarExplodedProcessor processor =\n        new StandardWarExplodedProcessor(Paths.get(\"ignore\"), Paths.get(\"ignore\"), APP_ROOT);\n    UnsupportedOperationException exception =\n        assertThrows(\n            UnsupportedOperationException.class,\n            () -> processor.computeEntrypoint(ImmutableList.of()));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Computing the entrypoint is currently not supported.\");\n  }\n\n  @Test\n  public void testGetJavaVersion() {\n    StandardWarExplodedProcessor processor =\n        new StandardWarExplodedProcessor(Paths.get(\"ignore\"), Paths.get(\"ignore\"), APP_ROOT);\n    UnsupportedOperationException exception =\n        assertThrows(UnsupportedOperationException.class, () -> processor.getJavaVersion());\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Getting the java version from a WAR file is currently not supported.\");\n  }\n\n  private static Path zipUpDirectory(Path sourceRoot, Path targetZip) throws IOException {\n    try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(targetZip))) {\n      for (Path source : new DirectoryWalker(sourceRoot).filterRoot().walk()) {\n\n        StringJoiner pathJoiner = new StringJoiner(\"/\", \"\", \"\");\n        sourceRoot.relativize(source).forEach(element -> pathJoiner.add(element.toString()));\n        String zipEntryPath =\n            Files.isDirectory(source) ? pathJoiner.toString() + '/' : pathJoiner.toString();\n\n        ZipEntry entry = new ZipEntry(zipEntryPath);\n        zipOut.putNextEntry(entry);\n        if (!Files.isDirectory(source)) {\n          try (InputStream in = Files.newInputStream(source)) {\n            ByteStreams.copy(in, zipOut);\n          }\n        }\n        zipOut.closeEntry();\n      }\n    }\n    return targetZip;\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/java/com/google/cloud/tools/jib/cli/war/WarFilesTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cli.war;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.cli.CommonCliOptions;\nimport com.google.cloud.tools.jib.cli.CommonContainerConfigCliOptions;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Optional;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link WarFiles}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class WarFilesTest {\n\n  @Mock private StandardWarExplodedProcessor mockStandardWarExplodedProcessor;\n  @Mock private CommonCliOptions mockCommonCliOptions;\n  @Mock private CommonContainerConfigCliOptions mockCommonContainerConfigCliOptions;\n  @Mock private ConsoleLogger mockLogger;\n\n  @Test\n  public void testToJibContainerBuilder_explodedStandard_basicInfo()\n      throws IOException, InvalidImageReferenceException {\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .setName(\"classes\")\n            .addEntry(\n                Paths.get(\"path/to/tempDirectory/WEB-INF/classes/class1.class\"),\n                AbsoluteUnixPath.get(\"/my/app/WEB-INF/classes/class1.class\"))\n            .build();\n    when(mockStandardWarExplodedProcessor.createLayers()).thenReturn(Arrays.asList(layer));\n    when(mockCommonContainerConfigCliOptions.isJettyBaseimage()).thenReturn(true);\n\n    JibContainerBuilder containerBuilder =\n        WarFiles.toJibContainerBuilder(\n            mockStandardWarExplodedProcessor,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"jetty\");\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\")\n        .inOrder();\n    assertThat(buildPlan.getLayers()).hasSize(1);\n    assertThat(buildPlan.getLayers().get(0).getName()).isEqualTo(\"classes\");\n    assertThat(((FileEntriesLayer) buildPlan.getLayers().get(0)).getEntries())\n        .containsExactlyElementsIn(\n            FileEntriesLayer.builder()\n                .addEntry(\n                    Paths.get(\"path/to/tempDirectory/WEB-INF/classes/class1.class\"),\n                    AbsoluteUnixPath.get(\"/my/app/WEB-INF/classes/class1.class\"))\n                .build()\n                .getEntries());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_optionalParameters()\n      throws IOException, InvalidImageReferenceException {\n    when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.of(\"base-image\"));\n    when(mockCommonContainerConfigCliOptions.getExposedPorts())\n        .thenReturn(ImmutableSet.of(Port.udp(123)));\n    when(mockCommonContainerConfigCliOptions.getVolumes())\n        .thenReturn(\n            ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")));\n    when(mockCommonContainerConfigCliOptions.getEnvironment())\n        .thenReturn(ImmutableMap.of(\"key1\", \"value1\"));\n    when(mockCommonContainerConfigCliOptions.getLabels())\n        .thenReturn(ImmutableMap.of(\"label\", \"mylabel\"));\n    when(mockCommonContainerConfigCliOptions.getUser()).thenReturn(Optional.of(\"customUser\"));\n    when(mockCommonContainerConfigCliOptions.getFormat()).thenReturn(Optional.of(ImageFormat.OCI));\n    when(mockCommonContainerConfigCliOptions.getProgramArguments())\n        .thenReturn(ImmutableList.of(\"arg1\"));\n    when(mockCommonContainerConfigCliOptions.getEntrypoint())\n        .thenReturn(ImmutableList.of(\"custom\", \"entrypoint\"));\n    when(mockCommonContainerConfigCliOptions.getCreationTime())\n        .thenReturn(Optional.of(Instant.ofEpochSecond(5)));\n\n    JibContainerBuilder containerBuilder =\n        WarFiles.toJibContainerBuilder(\n            mockStandardWarExplodedProcessor,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getBaseImage()).isEqualTo(\"base-image\");\n    assertThat(buildPlan.getExposedPorts()).isEqualTo(ImmutableSet.of(Port.udp(123)));\n    assertThat(buildPlan.getVolumes())\n        .isEqualTo(\n            ImmutableSet.of(AbsoluteUnixPath.get(\"/volume1\"), AbsoluteUnixPath.get(\"/volume2\")));\n    assertThat(buildPlan.getEnvironment()).isEqualTo(ImmutableMap.of(\"key1\", \"value1\"));\n    assertThat(buildPlan.getLabels()).isEqualTo(ImmutableMap.of(\"label\", \"mylabel\"));\n    assertThat(buildPlan.getUser()).isEqualTo(\"customUser\");\n    assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.OCI);\n    assertThat(buildPlan.getCmd()).isEqualTo(ImmutableList.of(\"arg1\"));\n    assertThat(buildPlan.getEntrypoint()).isEqualTo(ImmutableList.of(\"custom\", \"entrypoint\"));\n    assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.ofEpochSecond(5));\n  }\n\n  @Test\n  public void testToJibContainerBuilder_nonJettyBaseImageSpecifiedAndNoEntrypoint()\n      throws IOException, InvalidImageReferenceException {\n    JibContainerBuilder containerBuilder =\n        WarFiles.toJibContainerBuilder(\n            mockStandardWarExplodedProcessor,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(mockCommonContainerConfigCliOptions.isJettyBaseimage()).isFalse();\n    assertThat(buildPlan.getEntrypoint()).isNull();\n  }\n\n  @Test\n  public void testToJibContainerBuilder_noProgramArgumentsSpecified()\n      throws IOException, InvalidImageReferenceException {\n    JibContainerBuilder containerBuilder =\n        WarFiles.toJibContainerBuilder(\n            mockStandardWarExplodedProcessor,\n            mockCommonCliOptions,\n            mockCommonContainerConfigCliOptions,\n            mockLogger);\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n\n    assertThat(buildPlan.getCmd()).isNull();\n  }\n}\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/archiveLayerTest/layers.yaml",
    "content": "entries:\n  - name: \"default\"\n    archive: \"path/to/archive.tgz\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/default/layers.yaml",
    "content": "properties:\n  # file properties applied to all layers\n  filePermissions: \"644\"\n  directoryPermissions: \"755\"\n  user: \"0\"\n  group: \"0\"\n  timestamp: \"0\"\nentries:\n  - name: \"default\"\n    files:\n      - src: \"toFile.txt\"\n        dest: \"/target/toFile.txt\"\n      - src: \"toDir.txt\"\n        dest: \"/target/dir/\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/default/toDir.txt",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/default/toFile.txt",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/failWithExcludes/layers.yaml",
    "content": "entries:\n  - name: \"default\"\n    files:\n      - src: \"toFile.txt\"\n        dest: \"/target/toFile.txt\"\n        excludes:\n          - \"**/*\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/failWithExcludes/toFile.txt",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/failWithIncludes/layers.yaml",
    "content": "entries:\n  - name: \"default\"\n    files:\n      - src: \"toFile.txt\"\n        dest: \"/target/toFile.txt\"\n        includes:\n          - \"**/*\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/fileTest/failWithIncludes/toFile.txt",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/includesExcludesTest/layers.yaml",
    "content": "properties:\n  # file properties applied to all layers\n  filePermissions: \"644\"\n  directoryPermissions: \"755\"\n  user: \"0\"\n  group: \"0\"\n  timestamp: \"0\"\nentries:\n  - name: \"includes and excludes\"\n    files:\n      - src: \"project\"\n        dest: \"/target/ie\"\n        excludes:\n          - \"**/exclude.me\"\n        includes:\n          - \"**/*.me\"\n  - name: \"includes only\"\n    files:\n      - src: \"project\"\n        dest: \"/target/io\"\n        includes:\n          - \"**/include.me\"\n  - name: \"excludes only\"\n    files:\n      - src: \"project\"\n        dest: \"/target/eo\"\n        excludes:\n          - \"**/excludedDir/**\"\n  - name: \"excludes only shortcut\"\n    files:\n      - src: \"project\"\n        dest: \"/target/eo\"\n        excludes:\n          # equivalent to \"**/excludedDir/**\"\n          - \"**/excludedDir/\"\n  - name: \"exclude dir and contents\"\n    files:\n      - src: \"project\"\n        dest: \"/target/edac\"\n        excludes:\n          - \"**/excludedDir/**\"\n          - \"**/excludedDir\"\n  - name: \"excludes only wrong\"\n    files:\n      - src: \"project\"\n        dest: \"/target/eo\"\n        # this is not a safe excludes of directories\n        # only excludes the directory, but doesn't match any of the file contents\n        # and in the end, it will be included anyway because its children are included\n        excludes:\n          - \"**/excludedDir\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/includesExcludesTest/project/excludedDir/exclude.me",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/includesExcludesTest/project/includedDir/include.me",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/includesExcludesTest/project/wild.card",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/pathDoesNotExist/layers.yaml",
    "content": "entries:\n  - name: \"default\"\n    files:\n      - src: \"something-that-does-not-exist\"\n        dest: \"/target/\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/propertiesTest/dir/file.txt",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/propertiesTest/layers.yaml",
    "content": "properties:\n  filePermissions: \"000\"\n  directoryPermissions: \"700\"\n  user: \"0\"\n  group: \"0\"\n  timestamp: \"0\"\nentries:\n  - name: \"level 0 passthrough\"\n    files:\n      - src: \"dir\"\n        dest: \"/app/\"\n  - name: \"level 1 overrides\"\n    properties:\n      filePermissions: \"111\"\n      directoryPermissions: \"711\"\n      user: \"1\"\n      group: \"1\"\n      timestamp: \"1000\"\n    files:\n      - src: \"dir\"\n        dest: \"/app/\"\n  - name: \"level 2 overrides\"\n    properties:\n      filePermissions: \"111\"\n      directoryPermissions: \"711\"\n      user: \"1\"\n      group: \"1\"\n      timestamp: \"1000\"\n    files:\n      - src: \"dir\"\n        dest: \"/app/\"\n        properties:\n          filePermissions: \"222\"\n          directoryPermissions: \"722\"\n          user: \"2\"\n          group: \"2\"\n          timestamp: \"2000\"\n  - name: \"partial overrides\"\n    properties:\n      filePermissions: \"111\"\n      directoryPermissions: \"711\"\n    files:\n      - src: \"dir\"\n        dest: \"/app/\"\n        properties:\n          group: \"2\"\n          timestamp: \"2000\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/writeToRoot/dir/file.txt",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/layers/writeToRoot/layers.yaml",
    "content": "entries:\n  - name: \"root writer\"\n    files:\n      - src: \"dir\"\n        dest: \"/\"\n  - name: \"root parent fill\"\n    files:\n      - src: \".\"\n        dest: \"/\"\n        includes:\n          - \"**/file.txt\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/allDefaults/jib.yaml",
    "content": "apiVersion: jib/v1alpha1\nkind: BuildFile\n\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/allProperties/altYamls/alt-jib.yaml",
    "content": "# this buildfile doesn't necessarily work, just useful for testing parsing and translation\napiVersion: jib/v1alpha1\nkind: BuildFile\n\n# \"FROM\" with detail for manifest lists or multiple architectures\nfrom:\n  image: \"ubuntu\"\n  # optional: if missing, then defaults to `linux/amd64`\n  platforms:\n    - architecture: \"arm\"\n      os: \"linux\"\n    - architecture: \"amd64\"\n      os: \"darwin\"\n\n# potentially simple form of \"FROM\" (based on ability to define schema)\n# from: \"gcr.io/distroless/java:8\"\n\ncreationTime: 2000 # millis since epoch or iso8601 creation time\nformat: OCI # Docker or OCI\n\nenvironment:\n  \"KEY1\": \"v1\"\n  \"KEY2\": \"v2\"\nlabels:\n  \"label1\": \"l1\"\n  \"label2\": \"l2\"\nvolumes:\n  - \"/volume1\"\n  - \"/volume2\"\n\nexposedPorts:\n  - \"123/udp\"\n  - \"456\"\n  - \"789/tcp\"\n\nuser: \"customUser\"\nworkingDirectory: \"/home\"\nentrypoint:\n  - \"sh\"\n  - \"script.sh\"\ncmd:\n  - \"--param\"\n  - \"param\"\n\nlayers:\n  entries:\n    - name: \"scripts\"\n      files:\n        - src: \"project/script.sh\"\n          dest: \"/home/script.sh\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/allProperties/jib.yaml",
    "content": "# this buildfile doesn't necessarily work, just useful for testing parsing and translation\napiVersion: jib/v1alpha1\nkind: BuildFile\n\n# \"FROM\" with detail for manifest lists or multiple architectures\nfrom:\n  image: \"ubuntu\"\n  # optional: if missing, then defaults to `linux/amd64`\n  platforms:\n    - architecture: \"arm\"\n      os: \"linux\"\n    - architecture: \"amd64\"\n      os: \"darwin\"\n\n# potentially simple form of \"FROM\" (based on ability to define schema)\n# from: \"gcr.io/distroless/java:8\"\n\ncreationTime: 2000 # millis since epoch or iso8601 creation time\nformat: OCI # Docker or OCI\n\nenvironment:\n  \"KEY1\": \"v1\"\n  \"KEY2\": \"v2\"\nlabels:\n  \"label1\": \"l1\"\n  \"label2\": \"l2\"\nvolumes:\n  - \"/volume1\"\n  - \"/volume2\"\n\nexposedPorts:\n  - \"123/udp\"\n  - \"456\"\n  - \"789/tcp\"\n\nuser: \"customUser\"\nworkingDirectory: \"/home\"\nentrypoint:\n  - \"sh\"\n  - \"script.sh\"\ncmd:\n  - \"--param\"\n  - \"param\"\n\nlayers:\n  entries:\n    - name: \"scripts\"\n      files:\n        - src: \"project/script.sh\"\n          dest: \"/home/script.sh\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/allProperties/project/script.sh",
    "content": "env\necho \"string from file\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/templating/missingVar.yaml",
    "content": "apiVersion: jib/v1alpha1\nkind: BuildFile\n\nlabels:\n  \"label1\": \"${missingVar}\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/templating/multiLine.yaml",
    "content": "apiVersion: jib/v1alpha1\nkind: BuildFile\n\n${replace\nthis}\n"
  },
  {
    "path": "jib-cli/src/test/resources/buildfiles/projects/templating/valid.yaml",
    "content": "apiVersion: jib/v1alpha1\nkind: BuildFile\n\nlabels:\n  \"${key}\": \"${value}\"\n  \"label1\": \"${repeated}\"\n  \"label2\": \"${repeated}\"\n  \"label3\": \"$${escaped}\"\n  \"label4\": \"free$\"\n  \"unmatched\": \"${\"\n"
  },
  {
    "path": "jib-cli/src/test/resources/jar/standard/dependency1",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/jar/standard/dependency2",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/jar/standard/dependency3-SNAPSHOT-1.jar",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/jar/standard/directory/dependency4",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/war/standard/allLayers/META-INF/context.xml",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/war/standard/allLayers/Test.jsp",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/war/standard/allLayers/WEB-INF/classes/package/test.properties",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/war/standard/allLayers/WEB-INF/web.xml",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/war/standard/noWebInfClasses/META-INF/context.xml",
    "content": ""
  },
  {
    "path": "jib-cli/src/test/resources/war/standard/noWebInfLib/META-INF/context.xml",
    "content": ""
  },
  {
    "path": "jib-core/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n## 0.28.1\n\n### Added\n- feat: support Java 25 main methods\n\n### Changed\n- deps: update `org.ow2.asm:asm` to version 9.9\n\n## 0.28.0\n\n### Added\n- feat: add default base image for Java 25 (#4436)\n\n### Changed\n- deps: update `org.ow2.asm:asm` to version 9.8 for java 25 support\n\n## 0.27.3\n\n### Fixed\n- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4265)\n\n## 0.27.2\n- fix: allow pushing images with different arch/os to docker daemon [#4265](https://github.com/GoogleContainerTools/jib/issues/4265)\n- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4265)\n\n## 0.27.1\n\n### Fixed\n- fix: When building to the local docker daemon with multiple platforms configured, Jib will now automatically select the image that matches the OS type and architecture of the local Docker environment. ([#4249](https://github.com/GoogleContainerTools/jib/pull/4249))\n\n\n## 0.27.0\n\n### Changed\n- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204))\n\n### Fixed\n- fix: set PAX headers to address build reproducibility issue ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204))\n\n## 0.26.0\n\n### Fixed\n- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171))\n\n## 0.25.0\n\n### Changed\n- deps: bump com.github.luben:zstd-jni from 1.5.5-2 to 1.5.5-4. ([#4049](https://github.com/GoogleContainerTools/jib/pull/4049/))\n- deps: bump com.fasterxml.jackson:jackson-bom from 2.15.0 to 2.15.2. ([#4055](https://github.com/GoogleContainerTools/jib/pull/4055))\n- deps: bump com.google.guava:guava from 32.0.1-jre to 32.1.2-jre ([#4078](https://github.com/GoogleContainerTools/jib/pull/4078))\n- deps: bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9. ([#4098](https://github.com/GoogleContainerTools/jib/pull/4098))\n\n### Fixed\n- fix: fix WWW-Authenticate header parsing for Basic authentication ([#4035](https://github.com/GoogleContainerTools/jib/pull/4035/))\n\n## 0.24.0\n\n### Changed\n- Replaced deprecated usages of `com.google.api.client.util.Base64` with `java.util.Base64` ([#3872](https://github.com/GoogleContainerTools/jib/pull/3872))\n- Replaced deprecated usages of `ObjectMapper.configure` in jackson ([#3890](https://github.com/GoogleContainerTools/jib/pull/3890))\n\n### Fixed\n- Fixed `V22ManifestListTemplate` cast to allow pulling an OCI index manifest from cache ([#3974](https://github.com/GoogleContainerTools/jib/pull/3974))\n- Specified `CompressorStreamFactory` to decompress compressed layer until EOF in `CacheStorageWriter` ([#3983](https://github.com/GoogleContainerTools/jib/pull/3983))\n- Fixed multithreading issue from `DockerClientResolver.resolve` by not sharing a static `ServiceLoader` instance ([#3993](https://github.com/GoogleContainerTools/jib/pull/3993))\n\nThanks to our community contributors @Sineaggi, @rquinio, @patrickpichler, @erdi!\n\n## 0.23.0\n\n### Changed\n- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745))\n- Re-synchronized jackson dependencies with BOM to use latest versions ([#3768](https://github.com/GoogleContainerTools/jib/pull/3768))\n\n### Fixed\n- Fixed partially cached base image authorization issue by adding check for existence of layers in cache ([#3767](https://github.com/GoogleContainerTools/jib/pull/3767))\n\n## 0.22.0\n\n### Added\n- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)).\n- DockerClient interface which is used to make calls to the Docker daemon. This allows for custom implementations to be introduced via SPI ([#3703](https://github.com/GoogleContainerTools/jib/pull/3703)). \n- Support for OCI image index manifests ([#3715](https://github.com/GoogleContainerTools/jib/pull/3715)).\n- Support for base image layer compressed with zstd ([#3717](https://github.com/GoogleContainerTools/jib/pull/3717))\n\n### Changed\n- Upgraded slf4j-api to 2.0.0 ([#3734](https://github.com/GoogleContainerTools/jib/pull/3734), [#3735](https://github.com/GoogleContainerTools/jib/pull/3735)).\n- Upgraded nullaway to 0.9.9. ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720))\n- Jib now throws an exception when the base image doesn't support target platforms during multi-platform build ([#3707](https://github.com/GoogleContainerTools/jib/pull/3707)).\n- Jib now only checks for file existence instead of running the executable passed into `dockerClient.executable` for the purpose of verifying if docker is installed correctly. Users are responsible for ensuring that the docker executable specified through this property is valid and has the correct permissions ([#3744](https://github.com/GoogleContainerTools/jib/pull/3744)).\n\nThanks to our community contributors, @oliver-brm, @eddumelendez, @rquinio, @gsquared94!\n\n## 0.21.0\n\n### Added\n- Support for configuration of credential helper with environment variables ([#3575](https://github.com/GoogleContainerTools/jib/pull/3575)).\n- Support architecture suffixes in tags when publishing multi-platform images ([#3523](https://github.com/GoogleContainerTools/jib/pull/3523)).\n\n### Changed\n- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/pull/3612)).\n- Added helpful pointers for unsupported class file version exception cause ([#3499](https://github.com/GoogleContainerTools/jib/pull/3499)).\n\n\n## 0.20.0\n\n### Added\n\n- Increased robustness in registry communications by retrying HTTP requests (to the effect of retrying image pushes or pulls) on I/O exceptions with exponential backoffs. ([#3351](https://github.com/GoogleContainerTools/jib/pull/3351))\n\n### Changed\n\n- Downgraded Google HTTP libraries to 1.34.0 to resolve network issues. ([#3415](https://github.com/GoogleContainerTools/jib/pull/3415), [#3058](https://github.com/GoogleContainerTools/jib/issues/3058), [#3409](https://github.com/GoogleContainerTools/jib/issues/3409))\n\n## 0.19.0\n\n### Added\n\n- For Google Artifact Registry (`*-docker.pkg.dev`), Jib now tries [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) last like it has been doing for `gcr.io`. ([#3241](https://github.com/GoogleContainerTools/jib/pull/3241))\n\n### Changed\n\n- `JavaContainerBuilder#fromDistroless()` and `JavaContainerBuilder#fromDistrolessJetty()` are deprecated. To migrate, check the Javadoc. ([#3123](https://github.com/GoogleContainerTools/jib/pull/3123))\n- Timestamps of file entries in a built `TarImage` are set to the epoch, making the tarball reproducible. ([#3158](https://github.com/GoogleContainerTools/jib/issues/3158))\n\n## 0.18.0\n\n### Added\n\n- New method: `Containerizer#addRegistryMirrors` for configuring registry mirrors for base images. This is useful when hitting [Docker Hub rate limits](https://www.docker.com/increase-rate-limits). Only public mirrors (such as `mirror.gcr.io`) are supported. ([#2999](https://github.com/GoogleContainerTools/jib/issues/2999))\n\n## 0.17.0\n\n### Fixed\n\n- Updated jackson dependency version causing compatibility issues. ([#2931](https://github.com/GoogleContainerTools/jib/issues/2931))\n- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/issues/2819))\n- Fixed build failure when using a Docker daemon base image (`docker://...`) that has duplicate layers. ([#2829](https://github.com/GoogleContainerTools/jib/issues/2829))\n\n## 0.16.0\n\n### Added\n\n- Allow setting platform when building image from scratch. ([#2765](https://github.com/GoogleContainerTools/jib/issues/2765))\n- New system property `jib.skipExistingImages` (false by default) to skip pushing images (manifests) if the image already exists in the registry. ([#2360](https://github.com/GoogleContainerTools/jib/issues/2360))\n- _Incubating feature_: can now configure multiple platforms (such as architectures) to build multiple images as a bundle and push as a manifest list (also known as a fat manifest). As an incubating feature, there are certain limitations. For example, OCI image indices are not supported, and building a manifest list is supported only for registry pushing (using `RegistryImage`). ([#2523](https://github.com/GoogleContainerTools/jib/issues/2523), [#1567](https://github.com/GoogleContainerTools/jib/issues/1567))\n\n### Changed\n\n- Upgraded jib-build-plan to 0.4.0. ([#2660](https://github.com/GoogleContainerTools/jib/pull/2660))\n- Previous locally cached base image manifests will be ignored, as the caching mechanism changed to enable multi-platform image building. ([#2730](https://github.com/GoogleContainerTools/jib/pull/2730), [#2711](https://github.com/GoogleContainerTools/jib/pull/2711))\n- Upgraded the ASM library to 9.0 to resolve an issue when auto-inferring main class in Java 15+. ([#2776](https://github.com/GoogleContainerTools/jib/pull/2776))\n\n### Fixed\n\n- Fixed `NullPointerException` when the `\"auths\":` section in `~/.docker/config.json` has an entry with no `\"auth\":` field. ([#2535](https://github.com/GoogleContainerTools/jib/issues/2535))\n- Fixed `NullPointerException` to return a helpful message when a server does not provide any message in certain error cases (400 Bad Request, 404 Not Found, and 405 Method Not Allowed). ([#2532](https://github.com/GoogleContainerTools/jib/issues/2532))\n- Now supports sending client certificate (for example, via the `javax.net.ssl.keyStore` and `javax.net.ssl.keyStorePassword` system properties) and thus enabling mutual TLS authentication. ([#2585](https://github.com/GoogleContainerTools/jib/issues/2585), [#2226](https://github.com/GoogleContainerTools/jib/issues/2226))\n- Fixed `NullPointerException` during input validation (in Java 9+) when configuring Jib parameters using certain immutable collections (such as `List.of()`). ([#2702](https://github.com/GoogleContainerTools/jib/issues/2702))\n- Fixed authentication failure with Azure Container Registry when using [\"tokens\"](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-repository-scoped-permissions). ([#2784](https://github.com/GoogleContainerTools/jib/issues/2784))\n- Improved authentication flow for base image registry. ([#2134](https://github.com/GoogleContainerTools/jib/issues/2134))\n\n## 0.15.0\n\n### Added\n\n- Now sets configured file ownership when creating layer tars. ([#2499](https://github.com/GoogleContainerTools/jib/pull/2499))\n\n### Changed\n\n- `Ports.parse(List<String> ports)` now returns a `Set`(as a `HashSet`) instead of `ImmutableSet` ([#2513](https://github.com/GoogleContainerTools/jib/pull/2513))\n- Previous locally cached application layers will be ignored because of changes to the caching selectors. ([#2499](https://github.com/GoogleContainerTools/jib/pull/2499))\n\n### Fixed\n\n- Fixed authentication failure with Azure Container Registry when using an identity token defined in the `auths` section of Docker config (`~/.docker/config.json`). ([#2488](https://github.com/GoogleContainerTools/jib/pull/2488))\n- Now adding the Jib Core dependency transitively exposes the Build Plan API. ([#2507](https://github.com/GoogleContainerTools/jib/issues/2507))\n\n## 0.14.0\n\n### Added\n\n- Multiple additions to `ImageReference` to separate `tag` and `digest`. ([#1481](https://github.com/GoogleContainerTools/jib/issues/1481))\n    - `of(registry, repository, tag, digest)` to create an image from a tag and digest.\n    - `isValidDigest(digest)` to check if a string is a valid digest.\n    - `getDigest()` to get the digest.\n    - `parse()` now supports image reference strings containing both a tag and a digest.\n    - `getQualifier()` to return the digest, or the tag if no digest is set.\n    - `withQualifier()` to change the image's tag or digest (old behavior of `withTag()`)\n- New public API package hierarchy `com.google.cloud.tools.jib.api.buildplan` for [Container Build Plan Specification](https://github.com/GoogleContainerTools/jib/blob/2b2fe82ad0552ba3ce1de308a0fda24aab684365/proposals/container-build-plan-spec.md) and API.\n- `ContainerBuildPlan`, `FileEntriesLayer`, `FileEntry`, and `LayerObject` in `com.google.cloud.tools.jib.api.buildplan` as part of the Container Build Plan API. ([#2338](https://github.com/GoogleContainerTools/jib/pull/2338), [#2328](https://github.com/GoogleContainerTools/jib/pull/2328))\n\n### Changed\n\n- `ImageReference#toStringWithTag` has been renamed to `toStringWithQualifier`.\n- `ImageReference#isValidTag` no longer returns `true` for digests.\n- `ImageReference#isTagDigest` has been removed; use `#getDigest` with `Optional#isPresent()` to check if an `ImageReference` uses a digest.\n- `ImageReference#withTag` has been removed; use `withQualifier()` instead.\n- `ImageReference#isDefaultTag` and `usesDefaultTag` no longer return `true` for `null` or empty tags.\n- The following classes in `com.google.cloud.tools.jib.api` has been moved to `com.google.cloud.tools.jib.api.buildplan`: `AbsoluteUnixPath`, `RelativeUnixPath`, `Port`, `FilePermissions`, and `ImageFormat`. ([#2328](https://github.com/GoogleContainerTools/jib/pull/2328))\n- `LayerConfiguration` and `LayerEntry` are deprecated. Use `FileEntriesLayer` and `FileEntry` in `com.google.cloud.tools.jib.api.buildplan`. ([#2334](https://github.com/GoogleContainerTools/jib/pull/2334))\n\n### Fixed\n\n- Fixed the problem not inheriting `USER` container configuration from a base image. ([#2421](https://github.com/GoogleContainerTools/jib/pull/2421))\n- Fixed wrong capitalization of JSON properties in a loadable Docker manifest when building a tar image. ([#2430](https://github.com/GoogleContainerTools/jib/issues/2430))\n- Fixed an issue when using a base image whose image creation timestamp contains timezone offset. ([#2428](https://github.com/GoogleContainerTools/jib/issues/2428))\n\n## 0.13.1\n\n### Fixed\n\n- Fixed authentication failure with error `server did not return 'WWW-Authenticate: Bearer' header` in certain cases (for example, on OpenShift). ([#2258](https://github.com/GoogleContainerTools/jib/issues/2258))\n- Fixed an issue where using local Docker images (by `docker://...`) on Windows caused an error. ([#2270](https://github.com/GoogleContainerTools/jib/issues/2270))\n\n## 0.13.0\n\n### Added\n\n- New method: `JibContainerBuilder#describeContainer` which returns new class: `JibContainerDescription`, containing a selection of information used for the Jib build. ([#2115](https://github.com/GoogleContainerTools/jib/issues/2115))\n\n### Changed\n\n- Each local base image layer is pushed immediately after being compressed, rather than waiting for all layers to finish compressing before starting to push. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))\n- HTTP redirection URLs are no longer sanitized in order to work around an issue with certain registries that do not conform to HTTP standards. This resolves an issue with using Red Hat OpenShift and Quay registries. ([#2106](https://github.com/GoogleContainerTools/jib/issues/2106), [#1986](https://github.com/GoogleContainerTools/jib/issues/1986#issuecomment-547610104))\n- `Containerizer.DEFAULT_BASE_CACHE_DIRECTORY` has been changed on MacOS and Windows. ([#2216](https://github.com/GoogleContainerTools/jib/issues/2216))\n    - MacOS (`$XDG_CACHE_HOME` defined): from `$XDG_CACHE_HOME/google-cloud-tools-java/jib/` to `$XDG_CACHE_HOME/Google/Jib/`\n    - MacOS (`$XDG_CACHE_HOME` not defined): from `$HOME/Library/Application Support/google-cloud-tools-java/jib/` to `$HOME/Library/Caches/Google/Jib/`\n    - Windows (`$XDG_CACHE_HOME` defined): from `$XDG_CACHE_HOME\\google-cloud-tools-java\\jib\\` to `$XDG_CACHE_HOME\\Google\\Jib\\Cache\\`\n    - Windows (`$XDG_CACHE_HOME` not defined): from `%LOCALAPPDATA%\\google-cloud-tools-java\\jib\\` to `%LOCALAPPDATA%\\Google\\Jib\\Cache\\`\n\n### Fixed\n\n- `Containerizer#setAllowInsecureRegistries(boolean)` and the `sendCredentialsOverHttp` system property are now effective for authentication service server connections. ([#2074](https://github.com/GoogleContainerTools/jib/pull/2074))\n- Fixed inefficient communications when interacting with insecure registries and servers (when `Containerizer#setAllowInsecureRegistries(boolean)` is set). ([#946](https://github.com/GoogleContainerTools/jib/issues/946))\n- Building a tarball with `OCI` format now builds a correctly formatted OCI archive. ([#2124](https://github.com/GoogleContainerTools/jib/issues/2124))\n- Now automatically refreshes Docker registry authentication tokens when expired, fixing the issue that long-running builds may fail with \"401 unauthorized.\" ([#691](https://github.com/GoogleContainerTools/jib/issues/691))\n\n## 0.12.0\n\n### Added\n\n- Main class inference support for Java 13/14. ([#2015](https://github.com/GoogleContainerTools/jib/issues/2015))\n- `Containerizer#setAlwaysCacheBaseImage(boolean)` controls the optimization to skip downloading base image layers that exist in a target registry. ([#1870](https://github.com/GoogleContainerTools/jib/pull/1870))\n\n### Changed\n\n- Local base image layers are now processed in parallel, speeding up builds using large local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))\n- The base image manifest is no longer pulled from the registry if a digest is provided and the manifest is already cached. ([#1881](https://github.com/GoogleContainerTools/jib/issues/1881))\n- Docker daemon base images are now cached more effectively, speeding up builds using `DockerDaemonImage` base images. ([#1912](https://github.com/GoogleContainerTools/jib/issues/1912))\n- Now ignores `jib.alwaysCacheBaseImage` system property. Use `Containerizer#setAlwaysCacheBaseImage(boolean)` instead. ([#1870](https://github.com/GoogleContainerTools/jib/pull/1870))\n\n### Fixed\n\n- Fixed temporary directory cleanup during builds using local base images. ([#2016](https://github.com/GoogleContainerTools/jib/issues/2016))\n- Fixed additional tags being ignored when building to a tarball. ([#2043](https://github.com/GoogleContainerTools/jib/issues/2043))\n- Fixed `TarImage` base image failing if tar does not contain explicit directory entries. ([#2067](https://github.com/GoogleContainerTools/jib/issues/2067))\n\n## 0.11.0\n\n### Added\n\n- `Jib#from` and `JavaContainerBuilder#from` overloads to allow using a `DockerDaemonImage` or a `TarImage` as the base image. ([#1468](https://github.com/GoogleContainerTools/jib/issues/1468), [#1905](https://github.com/GoogleContainerTools/jib/issues/1905))\n- `Jib#from(String)` accepts strings prefixed with `docker://`, `tar://`, or `registry://` to specify image type.\n\n### Changed\n\n- To disable parallel execution, the property `jib.serialize` should be used instead of `jibSerialize`. ([#1968](https://github.com/GoogleContainerTools/jib/issues/1968))\n- `TarImage` is constructed using `TarImage.at(...).named(...)` instead of `TarImage.named(...).saveTo(...)`. ([#1918](https://github.com/GoogleContainerTools/jib/issues/1918))\n- For retrieving credentials from Docker config (`~/.docker/config.json`), `credHelpers` now takes precedence over `credsStore`, followed by `auths`. ([#1958](https://github.com/GoogleContainerTools/jib/pull/1958))\n- The legacy `credsStore` no longer requires defining empty registry entries in `auths` to be used. This now means that if `credsStore` is defined, `auths` will be completely ignored. ([#1958](https://github.com/GoogleContainerTools/jib/pull/1958))\n\n### Fixed\n\n- Fixed an issue interacting with certain registries due to changes to URL handling in the underlying Apache HttpClient library. ([#1924](https://github.com/GoogleContainerTools/jib/issues/1924))\n- Fixed the regression of slow network operations introduced at 0.10.1. ([#1980](https://github.com/GoogleContainerTools/jib/pull/1980))\n- Fixed an issue where connection timeout sometimes fell back to attempting plain HTTP (non-HTTPS) requests when the `Containerizer` is set to allow insecure registries. ([#1949](https://github.com/GoogleContainerTools/jib/pull/1949))\n\n## 0.10.1\n\n### Added\n\n- `JavaContainerBuilder#setLastModifiedTimeProvider` to set file timestamps. ([#1818](https://github.com/GoogleContainerTools/jib/pull/1818))\n\n### Changed\n\n- `JibContainerBuilder#addDependencies` is now split into three methods: `addDependencies`, `addSnapshotDependencies`, `addProjectDependencies`. ([#1773](https://github.com/GoogleContainerTools/jib/pull/1773))\n- For building and pushing to a registry, Jib now skips downloading and caching base image layers if the layers already exist in the target registry. This feature will be particularly useful in CI/CD environments. However, if you want to force caching base image layers locally, set the system property `-Djib.alwaysCacheBaseImage=true`. ([#1840](https://github.com/GoogleContainerTools/jib/pull/1840))\n\n### Fixed\n\n- Manifest lists referenced directly by sha256 are automatically parsed and the first `linux/amd64` manifest is used. ([#1811](https://github.com/GoogleContainerTools/jib/issues/1811))\n\n## 0.10.0\n\n### Added\n\n- `Containerizer#addEventHandler` for adding event handlers.\n\n### Changed\n\n- Multiple classes have been moved to the `com.google.cloud.tools.jib.api` package.\n- Event handlers are now added directly to the `Containerizer` rather than adding them to an `EventHandlers` object first.\n- Removed multiple classes to simplify the event system (`JibEventType`, `BuildStepType`, `EventDispatcher`, `DefaultEventDispatcher`, `LayerCountEvent`)\n- MainClassFinder now uses a static method instead of requiring instantiation.\n\n## 0.9.2\n\n### Added\n\n- Container configurations in the base image are now propagated when registry uses the old V2 image manifest, schema version 1 (such as Quay). ([#1641](https://github.com/GoogleContainerTools/jib/issues/1641))\n- `Containerizer#setOfflineMode` to retrieve the base image from Jib's cache rather than a container registry. ([#718](https://github.com/GoogleContainerTools/jib/issues/718))\n\n### Fixed\n\n- Labels in the base image are now propagated. ([#1643](https://github.com/GoogleContainerTools/jib/issues/1643))\n- Fixed an issue with using OCI base images. ([#1683](https://github.com/GoogleContainerTools/jib/issues/1683))\n\n## 0.9.1\n\n### Added\n\n- Overloads for `LayerConfiguration#addEntryRecursive` that take providers to set file permissions and file modification time on a per-file basis. ([#1607](https://github.com/GoogleContainerTools/jib/issues/1607))\n\n### Changed\n\n- `LayerConfiguration` takes file modification time as an `Instant` instead of a `long`.\n\n### Fixed\n\n- Fixed an issue where automatically generated parent directories in a layer did not get their timestamp configured correctly to epoch + 1s. ([#1648](https://github.com/GoogleContainerTools/jib/issues/1648))\n- Fixed an issue where the library creates wrong images by adding base image layers in reverse order when registry uses the old V2 image manifest, schema version 1 (such as Quay). ([#1627](https://github.com/GoogleContainerTools/jib/issues/1627))\n\n## 0.9.0\n\n### Added\n\n- `JavaContainerBuilder#setAppRoot()` and `JavaContainerBuilder#fromDistrolessJetty()` for building WAR containers. ([#1464](https://github.com/GoogleContainerTools/jib/issues/1464))\n- `Jib#fromScratch()` to start building from an empty base image. ([#1471](https://github.com/GoogleContainerTools/jib/issues/1471))\n- Methods in `JavaContainerBuilder` for setting the destination directories for classes, resources, directories, and additional classpath files.\n\n### Changed\n\n- Allow skipping `JavaContainerBuilder#setMainClass()` to skip setting the entrypoint.\n- `os` and `architecture` are taken from base image. ([#1564](https://github.com/GoogleContainerTools/jib/pull/1564))\n\n### Fixed\n\n- `ImageReference` assumes `registry-1.docker.io` as the registry if the host part of an image reference is `docker.io`. ([#1549](https://github.com/GoogleContainerTools/jib/issues/1549))\n\n## 0.1.2\n\n### Added\n\n- `ProgressEvent#getBuildStepType` method to get which step in the build process a progress event corresponds to. ([#1449](https://github.com/GoogleContainerTools/jib/pull/1449))\n- `LayerCountEvent` that is dispatched at the beginning of certain pull/build/push build steps to indicate the number of layers being processed. ([#1461](https://github.com/GoogleContainerTools/jib/pull/1461))\n\n### Changed\n\n- `JibContainerBuilder#containerize()` throws multiple sub-types of `RegistryException` rather than wrapping them in an `ExecutionException`. ([#1440](https://github.com/GoogleContainerTools/jib/issues/1440))\n\n### Fixed\n\n- `MainClassFinder` failure when main method is defined using varargs (i.e. `public static void main(String... args)`). ([#1456](https://github.com/GoogleContainerTools/jib/issues/1456))\n\n## 0.1.1\n\n### Added\n\n- Adds support for configuring volumes. ([#1121](https://github.com/GoogleContainerTools/jib/issues/1121))\n- Adds `JavaContainerBuilder` for building opinionated containers for Java applications. ([#1212](https://github.com/GoogleContainerTools/jib/issues/1212))\n"
  },
  {
    "path": "jib-core/README.md",
    "content": "![experimental](https://img.shields.io/badge/stability-beta-orange.svg)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.google.cloud.tools/jib-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.google.cloud.tools/jib-core)\n[![Gitter version](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/google/jib)\n\n# Jib Core - Java library for building containers\n\nJib Core is a Java library for building Docker and [OCI](https://github.com/opencontainers/image-spec) container images. It implements a general-purpose container builder that can be used to build containers without a Docker daemon, for any application. The implementation is pure Java.\n\n*The API is currently in beta and may change substantially.*\n\nJib Core powers the popular Jib plugins for Maven and Gradle. The plugins build containers specifically for JVM languages and separate the application into multiple layers to optimize for fast rebuilds.\\\nFor the Maven plugin, see the [jib-maven-plugin project](../jib-maven-plugin).\\\nFor the Gradle plugin, see the [jib-gradle-plugin project](../jib-gradle-plugin).\n\nFor information about the Jib project, see the [Jib project README](../README.md).\n\n## Adding Jib Core to your build\n\nAdd Jib Core as a dependency using Maven:\n\n```xml\n<dependency>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>jib-core</artifactId>\n  <version>0.28.1</version>\n</dependency>\n```\n\nAdd Jib Core as a dependency using Gradle:\n\n```groovy\ndependencies {\n  compile 'com.google.cloud.tools:jib-core:0.28.1'\n}\n```\n\n## Examples\n\n```java\nJib.from(\"busybox\")\n   .addLayer(Arrays.asList(Paths.get(\"helloworld.sh\")), AbsoluteUnixPath.get(\"/\")) \n   .setEntrypoint(\"sh\", \"/helloworld.sh\")\n   .containerize(\n       Containerizer.to(RegistryImage.named(\"gcr.io/my-project/hello-from-jib\")\n                                     .addCredential(\"myusername\", \"mypassword\")));\n```\n\n1. [`Jib.from(\"busybox\")`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Jib.html#from-java.lang.String-) creates a new [`JibContainerBuilder`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/0.1.0/com/google/cloud/tools/jib/api/JibContainerBuilder.html) configured with [`busybox`](https://hub.docker.com/_/busybox/) as the base image.\n1. [`.addLayer(...)`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JibContainerBuilder.html#addLayer-java.util.List-com.google.cloud.tools.jib.api.AbsoluteUnixPath-) configures the `JibContainerBuilder` with a new layer with `helloworld.sh` (local file) to be placed into the container at `/helloworld.sh`.\n1. [`.setEntrypoint(\"sh\", \"/helloworld.sh\")`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JibContainerBuilder.html#setEntrypoint-java.lang.String...-) sets the entrypoint of the container to run `/helloworld.sh`.\n1. [`RegistryImage.named(\"gcr.io/my-project/hello-from-jib\")`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/RegistryImage.html#named-java.lang.String-) creates a new [`RegistryImage`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/RegistryImage.html) configured with `gcr.io/my-project/hello-from-jib` as the target image to push to.\n1. [`.addCredential`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/RegistryImage.html#addCredential-java.lang.String-java.lang.String-) adds the username/password credentials to authenticate the push to `gcr.io/my-project/hello-from-jib`. See [`CredentialRetrieverFactory`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.html) for common credential retrievers (to retrieve credentials from Docker config or credential helpers, for example). These credential retrievers can be used with [`.addCredentialRetriever`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/RegistryImage.html#addCredentialRetriever-com.google.cloud.tools.jib.api.CredentialRetriever-).\n1. [`Containerizer.to`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Containerizer.html#to-com.google.cloud.tools.jib.api.RegistryImage-) creates a new [`Containerizer`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Containerizer.html) configured to push to the `RegistryImage`.\n1. [`.containerize`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JibContainerBuilder.html#containerize-com.google.cloud.tools.jib.api.Containerizer-) executes the containerization. If successful, the container image will be available at `gcr.io/my-project/hello-from-jib`.\n\nSee [examples](examples/README.md) for links to more jib-core samples. We welcome contributions for additional examples and tutorials!\n\n## API overview\n\n[`Jib`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Jib.html) - the main entrypoint for using Jib Core\n\n[`JibContainerBuilder`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JibContainerBuilder.html) - configures the container to build\n\n[`Containerizer`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Containerizer.html) - configures how and where to containerize to\n\n[`JibContainer`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JibContainer.html) - information about the built container\n\nThree types define what Jib can accept as either the base image or as the build target:\n- [`RegistryImage`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/RegistryImage.html) - an image on a container registry\n- [`DockerDaemonImage`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/DockerDaemonImage.html) - an image in the Docker daemon\n- [`TarImage`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/TarImage.html) - an image saved as a tarball archive on the filesystem\n\nOther useful classes:\n- [`ImageReference`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/ImageReference.html) - represents an image reference and has useful methods for parsing and manipulating image references\n- [`LayerConfiguration`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/LayerConfiguration.html) - configures a container layer to build\n- [`CredentialRetriever`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/CredentialRetriever.html) - implement with custom credential retrieval methods for authenticating against a container registry\n- [`CredentialRetrieverFactory`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.html) - provides useful `CredentialRetriever`s to retrieve credentials from Docker config and credential helpers\n\nJava-specific API:\n- [`JavaContainerBuilder`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JavaContainerBuilder.html) - configures a `JibContainerBuilder` for Java-specific applications\n- [`MainClassFinder`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/MainClassFinder.html) - find the main Java class in a given list of class files\n\n## API reference\n\n[API reference](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/package-summary.html)\n\n## How Jib Core works\n\nThe Jib Core system consists 3 main parts:\n\n- an execution orchestrator that executes an asynchronous pipeline of containerization steps,\n- an image manipulator capable of handling Docker and OCI image formats, and\n- a registry client that implements the [Docker Registry V2 API](https://docs.docker.com/registry/spec/api/).\n\nSome other parts of Jib Core internals include:\n\n- a caching mechanism to speed up builds (configurable with [`Containerizer.setApplicationLayersCache`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Containerizer.html#setApplicationLayersCache-java.nio.file.Path-) and [`Containerizer.setBaseImageLayersCache`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Containerizer.html#setBaseImageLayersCache-java.nio.file.Path-))\n- an [eventing system](#events) to react to events from Jib Core during its execution (add handlers with [`Containerizer.addEventHandler`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/Containerizer.html#addEventHandler-java.lang.Class-java.util.function.Consumer-))\n- support for fully-concurrent multi-threaded executions\n\n## Events\n\nThroughout the build process, Jib Core dispatches events that provide useful information. These events implement the type [`JibEvent`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/JibEvent.html), and can be handled by registering event handlers with the containerizer.\n\n```java\nJib.from(...)\n    ...\n    .containerize(\n        Containerizer.to(...)\n            ...\n            .addEventHandler(LogEvent.class, logEvent -> System.out.println(logEvent.getLevel() + \": \" + logEvent.getMessage())\n            .addEventHandler(TimerEvent.class, timeEvent -> ...));\n```\n\nWhen Jib dispatches events, the event handlers you defined for that event type will be called. The following are the types of events you can listen for in Jib core (see [API reference](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/package-summary.html) for more information):\n\n- [`LogEvent`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/api/LogEvent.html) - Log message events. The message and verbosity can be retrieved using `getMessage()` and `getLevel()`, respectively.\n- [`TimerEvent`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/event/events/TimerEvent.html) (*Incubating*) - Events used for measuring how long different build steps take. You can retrieve the duration since the timer's creation and the duration since the same timer's previous event using `getElapsed()` and `getDuration()`, respectively.\n- [`ProgressEvent`](http://www.javadoc.io/page/com.google.cloud.tools/jib-core/latest/com/google/cloud/tools/jib/event/events/ProgressEvent.html) (*Incubating*) - Indicates the amount of progress build steps have made. Each progress event consists of an allocation (containing a fraction representing how much of the root allocation this allocation accounts for) and a number of progress units that indicates the amount of work completed since the previous progress event. In other words, the amount of work a single progress event has completed (out of 1.0) can be calculated using `getAllocation().getFractionOfRoot() * getUnits()`.\n\n## Frequently Asked Questions (FAQ)\n\nSee the [Jib project FAQ](../docs/faq.md).\n\n## Upcoming features\n\n- Extensions to make building Java and other language-specific containers easier\n\nSee [Milestones](https://github.com/GoogleContainerTools/jib/milestones) for planned features. [Get involved with the community](https://github.com/GoogleContainerTools/jib/tree/master#get-involved-with-the-community) for the latest updates.\n\n## Community\n\nSee the [Jib project README](/../../#community).\n"
  },
  {
    "path": "jib-core/build.gradle",
    "content": "plugins {\n  id 'net.researchgate.release'\n  id 'maven-publish'\n  id 'eclipse'\n}\n\njava {\n  // Feature to handle base image layers compressed with zstd instead of gzip\n  // Will need to re-assess the optional dependency when zstd becomes widely used\n  registerFeature('zstdSupport') {\n    usingSourceSet(sourceSets.main)\n  }\n}\n\ndependencies {\n  api dependencyStrings.BUILD_PLAN\n  implementation dependencyStrings.GOOGLE_HTTP_CLIENT\n  implementation dependencyStrings.GOOGLE_HTTP_CLIENT_APACHE_V2\n  implementation dependencyStrings.GOOGLE_AUTH_LIBRARY_OAUTH2_HTTP\n\n  implementation dependencyStrings.COMMONS_COMPRESS\n  zstdSupportImplementation  dependencyStrings.ZSTD_JNI\n  implementation dependencyStrings.GUAVA\n  implementation(platform(dependencyStrings.JACKSON_BOM))\n  implementation dependencyStrings.JACKSON_DATABIND\n  implementation dependencyStrings.JACKSON_DATATYPE_JSR310\n  implementation dependencyStrings.ASM\n\n  testImplementation dependencyStrings.JUNIT\n  testImplementation dependencyStrings.TRUTH\n  testImplementation dependencyStrings.TRUTH8\n  testImplementation dependencyStrings.MOCKITO_CORE\n  testImplementation dependencyStrings.SLF4J_API\n  testImplementation dependencyStrings.SYSTEM_RULES\n  testImplementation dependencyStrings.ZSTD_JNI\n\n  integrationTestImplementation dependencyStrings.JBCRYPT\n}\n\njar {\n  manifest {\n    attributes 'Implementation-Version': archiveVersion\n    attributes 'Automatic-Module-Name': 'com.google.cloud.tools.jib'\n\n    // OSGi metadata\n    attributes 'Bundle-SymbolicName': 'com.google.cloud.tools.jib'\n    attributes 'Bundle-Name': 'Jib library for building Docker and OCI images'\n    attributes 'Bundle-Vendor': 'Google LLC'\n    attributes 'Bundle-DocURL': 'https://github.com/GoogleContainerTools/jib'\n    attributes 'Bundle-License': 'https://www.apache.org/licenses/LICENSE-2.0'\n    attributes 'Export-Package': 'com.google.cloud.tools.jib.*'\n  }\n}\n\n/* RELEASE */\nconfigureMavenRelease()\n\npublishing {\n  publications {\n    mavenJava(MavenPublication) {\n      pom {\n        name = 'Jib Core'\n        description = 'Build container images.'\n      }\n      from components.java\n    }\n  }\n}\n\n// Release plugin (git release commits and version updates)\nrelease {\n  tagTemplate = 'v$version-core'\n  git {\n    requireBranch = /^core-release-v\\d+.*$/  //regex\n  }\n}\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-core/examples/README.md",
    "content": "# Jib Core examples\n\nPlease [file an issue](/../../issues/new) if you find any problems with the examples or would like to request other examples.\n\nFor examples on using Jib plugins, see [examples](../../examples).\n\n### [sbt](https://index.scala-lang.org/schmitch/sbt-jib) plugin\n\nJib Core is used in a third-party project, [sbt-jib](https://github.com/schmitch/sbt-jib), that brings the benefits of Jib to [sbt](https://www.scala-sbt.org/) users.\n\n### Cram\n\nSee [Cram](https://github.com/briandealwis/cram) for a simple example of a command-line utility that uses Jib Core to build Docker containers from file system contents.\n\n### Using Jib Core in Gradle builds\n\nSee [build.gradle](build.gradle) for examples using Jib Core to build and manipulate container images within a `build.gradle` script.\n\n### AutoJib - make your application self-containerize\n\nSee [AutoJib](https://github.com/coollog/autojib) for an example of a library that, when added as a dependency to a Java application, can make your application containerize itself.\n"
  },
  {
    "path": "jib-core/examples/build.gradle/README.md",
    "content": "# Examples using Jib Core in Gradle builds\n\nJib Core is a containerization library for JVM languages, so it works well in Groovy as well. You can use Jib Core directly within a Gradle `build.gradle` to make tasks that build and manipulate container images.\n\nFor example, the following snippet is a simple example that creates a Gradle task that adds an environment variable to an existing image:\n\n`build.gradle`:\n\n```groovy\n// Imports Jib Core as a library to use in this build script.\nbuildscript {\n  repositories {\n    mavenLocal()\n    mavenCentral()\n  }\n  dependencies {\n    classpath 'com.google.cloud.tools:jib-core:0.27.0'\n  }\n}\n\nimport com.google.cloud.tools.jib.api.Jib\nimport com.google.cloud.tools.jib.api.Containerizer\nimport com.google.cloud.tools.jib.api.ImageReference\nimport com.google.cloud.tools.jib.api.RegistryImage\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory\n\n// Creates a task called 'dojib'.\ntask('dojib') {\n  doLast { \n    def targetImage = '<target image reference>'\n    Jib\n        // Starts with the existing image.\n        .from('<existing image reference')\n        // Adds an environment variable.\n        .addEnvironmentVariable('ENV_NAME', 'ENV_VALUE')\n        // Performs the containerization.\n        .containerize(\n            Containerizer.to(\n                // Tells Jib to containerize to targetImage's registry. \n                RegistryImage\n                    .named(targetImage)\n                    // Tells Jib to get registry credentials from a Docker config.\n                    .addCredentialRetriever(\n                        CredentialRetrieverFactory\n                            .forImage(\n                                    ImageReference.parse(targetImage),\n                                    logEvent -> logger.log(LogLevel.valueOf(logEvent.getLevel().name()), logEvent.getMessage()))\n                            .dockerConfig())))\n    println 'done'\n  }\n}\n```\n"
  },
  {
    "path": "jib-core/gradle.properties",
    "content": "version = 0.28.2-SNAPSHOT\n"
  },
  {
    "path": "jib-core/kokoro/release_build.sh",
    "content": "#!/bin/bash\n\n# Fail on any error.\nset -o errexit\n# Display commands to stderr.\nset -o xtrace\n\ncd github/jib\n./gradlew :jib-core:prepareRelease\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/Command.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib;\n\nimport com.google.common.io.CharStreams;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** Test utility to run shell commands for integration tests. */\npublic class Command {\n\n  private final List<String> command;\n  private Path workingDir = null;\n\n  /** Instantiate with a command. */\n  public Command(String... command) {\n    this.command = Arrays.asList(command);\n  }\n\n  /** Instantiate with a command. */\n  public Command(List<String> command) {\n    this.command = command;\n  }\n\n  public Command setWorkingDir(Path workingDir) {\n    this.workingDir = workingDir;\n    return this;\n  }\n\n  /** Runs the command. */\n  public String run() throws IOException, InterruptedException {\n    return run(null);\n  }\n\n  /** Runs the command and pipes in {@code stdin}. */\n  public String run(@Nullable byte[] stdin) throws IOException, InterruptedException {\n    ProcessBuilder processBuilder = new ProcessBuilder(command);\n    if (workingDir != null) {\n      processBuilder.directory(workingDir.toFile());\n    }\n    Process process = processBuilder.start();\n\n    if (stdin != null) {\n      // Write out stdin.\n      try (OutputStream outputStream = process.getOutputStream()) {\n        outputStream.write(stdin);\n      }\n    }\n\n    // Read in stdout.\n    try (InputStreamReader inputStreamReader =\n        new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)) {\n      String output = CharStreams.toString(inputStreamReader);\n\n      if (process.waitFor() != 0) {\n        String stderr =\n            CharStreams.toString(\n                new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8));\n        throw new RuntimeException(\"Command '\" + String.join(\" \", command) + \"' failed: \" + stderr);\n      }\n\n      return output;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/IntegrationTestingConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib;\n\nimport com.google.common.base.Strings;\nimport org.junit.Assert;\n\n/** Configuration for integration tests. */\npublic class IntegrationTestingConfiguration {\n\n  public static String getTestRepositoryLocation() {\n    String projectId = System.getenv(\"JIB_INTEGRATION_TESTING_PROJECT\");\n    if (!Strings.isNullOrEmpty(projectId)) {\n      return \"gcr.io/\" + projectId;\n    }\n    String location = System.getenv(\"JIB_INTEGRATION_TESTING_LOCATION\");\n    if (Strings.isNullOrEmpty(location)) {\n      Assert.fail(\n          \"Must set environment variable JIB_INTEGRATION_TESTING_PROJECT to the \"\n              + \"GCP project to use for integration testing or \"\n              + \"JIB_INTEGRATION_TESTING_LOCATION to a suitable registry/repository location.\");\n    }\n    return location;\n  }\n\n  private IntegrationTestingConfiguration() {}\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ContainerizerIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.cloud.tools.jib.event.progress.ProgressEventHandler;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.registry.LocalRegistry;\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.stream.Stream;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.rules.TemporaryFolder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n// TODO: now it looks like we can move everything here into JibIntegrationTest.\n/** Integration tests for {@link Containerizer}. */\npublic class ContainerizerIntegrationTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n  private final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  /**\n   * Helper class to hold a {@link ProgressEventHandler} and verify that it handles a full progress.\n   */\n  private static class ProgressChecker {\n\n    private final ProgressEventHandler progressEventHandler =\n        new ProgressEventHandler(\n            update -> {\n              lastProgress = update.getProgress();\n              areTasksFinished = update.getUnfinishedLeafTasks().isEmpty();\n            });\n\n    private volatile double lastProgress = 0.0;\n    private volatile boolean areTasksFinished = false;\n\n    private void checkCompletion() {\n      Assert.assertEquals(1.0, lastProgress, DOUBLE_ERROR_MARGIN);\n      Assert.assertTrue(areTasksFinished);\n    }\n  }\n\n  @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);\n\n  private static final Logger logger = LoggerFactory.getLogger(ContainerizerIntegrationTest.class);\n  private static final String DISTROLESS_DIGEST =\n      \"sha256:f488c213f278bc5f9ffe3ddf30c5dbb2303a15a74146b738d12453088e662880\";\n  private static final double DOUBLE_ERROR_MARGIN = 1e-10;\n\n  public static ImmutableList<FileEntriesLayer> fakeLayerConfigurations;\n\n  @BeforeClass\n  public static void setUp() throws URISyntaxException, IOException {\n    fakeLayerConfigurations =\n        ImmutableList.of(\n            makeLayerConfiguration(\"core/application/dependencies\", \"/app/libs/\"),\n            makeLayerConfiguration(\"core/application/resources\", \"/app/resources/\"),\n            makeLayerConfiguration(\"core/application/classes\", \"/app/classes/\"));\n  }\n\n  /**\n   * Lists the files in the {@code resourcePath} resources directory and builds a {@link\n   * FileEntriesLayer} from those files.\n   */\n  private static FileEntriesLayer makeLayerConfiguration(\n      String resourcePath, String pathInContainer) throws URISyntaxException, IOException {\n    try (Stream<Path> fileStream =\n        Files.list(Paths.get(Resources.getResource(resourcePath).toURI()))) {\n      FileEntriesLayer.Builder layerConfigurationBuilder = FileEntriesLayer.builder();\n      fileStream.forEach(\n          sourceFile ->\n              layerConfigurationBuilder.addEntry(\n                  sourceFile, AbsoluteUnixPath.get(pathInContainer + sourceFile.getFileName())));\n      return layerConfigurationBuilder.build();\n    }\n  }\n\n  private static void assertDockerInspect(String imageReference)\n      throws IOException, InterruptedException {\n    String dockerInspectExposedPorts =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.ExposedPorts}}'\", imageReference)\n            .run();\n    String dockerInspectLabels =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Labels}}'\", imageReference).run();\n    String dockerConfigEnv =\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.Env}}\", imageReference).run();\n    String history = new Command(\"docker\", \"history\", imageReference).run();\n\n    MatcherAssert.assertThat(\n        dockerInspectExposedPorts,\n        CoreMatchers.containsString(\n            \"\\\"1000/tcp\\\":{},\\\"2000/tcp\\\":{},\\\"2001/tcp\\\":{},\\\"2002/tcp\\\":{},\\\"3000/udp\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectLabels,\n        CoreMatchers.containsString(\"\\\"key1\\\":\\\"value1\\\",\\\"key2\\\":\\\"value2\\\"\"));\n    MatcherAssert.assertThat(dockerConfigEnv, CoreMatchers.containsString(\"env1=envvalue1\"));\n    MatcherAssert.assertThat(dockerConfigEnv, CoreMatchers.containsString(\"env2=envvalue2\"));\n    MatcherAssert.assertThat(history, CoreMatchers.containsString(\"jib-integration-test\"));\n    MatcherAssert.assertThat(history, CoreMatchers.containsString(\"bazel build ...\"));\n  }\n\n  private static void assertLayerSize(int expected, String imageReference)\n      throws IOException, InterruptedException {\n    Command command =\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{join .RootFS.Layers \\\",\\\"}}\", imageReference);\n    String layers = command.run().trim();\n    Assert.assertEquals(expected, Splitter.on(\",\").splitToList(layers).size());\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private ProgressChecker progressChecker = new ProgressChecker();\n\n  @Test\n  public void testSteps_forBuildToDockerRegistry()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    System.setProperty(\"jib.alwaysCacheBaseImage\", \"true\");\n    String imageReference = dockerHost + \":\" + \"5000/testimage:testtag\";\n    Path cacheDirectory = temporaryFolder.newFolder().toPath();\n    Containerizer containerizer =\n        Containerizer.to(RegistryImage.named(imageReference))\n            .setBaseImageLayersCache(cacheDirectory)\n            .setApplicationLayersCache(cacheDirectory);\n\n    long lastTime = System.nanoTime();\n    JibContainer image1 =\n        buildImage(\n            ImageReference.of(\"gcr.io\", \"distroless/java\", DISTROLESS_DIGEST),\n            containerizer,\n            Collections.emptyList());\n\n    progressChecker.checkCompletion();\n    progressChecker = new ProgressChecker(); // to reset\n\n    logger.info(\"Initial build time: \" + ((System.nanoTime() - lastTime) / 1_000_000));\n\n    lastTime = System.nanoTime();\n    JibContainer image2 =\n        buildImage(\n            ImageReference.of(\"gcr.io\", \"distroless/java\", DISTROLESS_DIGEST),\n            containerizer,\n            Collections.emptyList());\n\n    logger.info(\"Secondary build time: \" + ((System.nanoTime() - lastTime) / 1_000_000));\n\n    Assert.assertEquals(image1, image2);\n\n    localRegistry.pull(imageReference);\n    assertDockerInspect(imageReference);\n    assertLayerSize(7, imageReference);\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\", new Command(\"docker\", \"run\", \"--rm\", imageReference).run());\n\n    String imageReferenceByDigest = dockerHost + \":5000/testimage@\" + image1.getDigest();\n    localRegistry.pull(imageReferenceByDigest);\n    assertDockerInspect(imageReferenceByDigest);\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", imageReferenceByDigest).run());\n  }\n\n  @Test\n  public void testSteps_forBuildToDockerRegistry_multipleTags()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    buildImage(\n        ImageReference.of(\"gcr.io\", \"distroless/java\", DISTROLESS_DIGEST),\n        Containerizer.to(RegistryImage.named(dockerHost + \":5000/testimage:testtag\")),\n        Arrays.asList(\"testtag2\", \"testtag3\"));\n\n    String imageReference = dockerHost + \":5000/testimage:testtag\";\n    localRegistry.pull(imageReference);\n    assertDockerInspect(imageReference);\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\", new Command(\"docker\", \"run\", \"--rm\", imageReference).run());\n\n    String imageReference2 = dockerHost + \":5000/testimage:testtag2\";\n    localRegistry.pull(imageReference2);\n    assertDockerInspect(imageReference2);\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", imageReference2).run());\n\n    String imageReference3 = dockerHost + \":5000/testimage:testtag3\";\n    localRegistry.pull(imageReference3);\n    assertDockerInspect(imageReference3);\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", imageReference3).run());\n  }\n\n  @Test\n  public void testSteps_forBuildToDockerRegistry_skipExistingDigest()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, \"true\");\n\n    JibContainer image1 =\n        buildImage(\n            ImageReference.scratch(),\n            Containerizer.to(RegistryImage.named(dockerHost + \":5000/testimagerepo:testtag\")),\n            Collections.singletonList(\"testtag2\"));\n\n    // Test that the initial image with the original tag has been pushed.\n    localRegistry.pull(dockerHost + \":5000/testimagerepo:testtag\");\n    // Test that any additional tags have also been pushed with the original image.\n    localRegistry.pull(dockerHost + \":5000/testimagerepo:testtag2\");\n\n    // Push the same image with a different tag, with SKIP_EXISTING_IMAGES enabled.\n    JibContainer image2 =\n        buildImage(\n            ImageReference.scratch(),\n            Containerizer.to(RegistryImage.named(dockerHost + \":5000/testimagerepo:new_testtag\")),\n            Collections.emptyList());\n\n    // Test that the pull request throws an exception, indicating that the new tag was not pushed.\n    try {\n      localRegistry.pull(dockerHost + \":5000/testimagerepo:new_testtag\");\n      Assert.fail(\n          \"jib.skipExistingImages was enabled and digest was already pushed, \"\n              + \"hence new_testtag shouldn't have been pushed.\");\n    } catch (RuntimeException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"manifest for \" + dockerHost + \":5000/testimagerepo:new_testtag not found\"));\n    }\n\n    // Test that both images have the same properties.\n    Assert.assertEquals(image1.getDigest(), image2.getDigest());\n    Assert.assertEquals(image1.getImageId(), image2.getImageId());\n\n    // Test that the first image was pushed while the second one was skipped\n    Assert.assertTrue(image1.isImagePushed());\n    Assert.assertFalse(image2.isImagePushed());\n  }\n\n  @Test\n  public void testBuildToDockerRegistry_dockerHubBaseImage()\n      throws InvalidImageReferenceException, IOException, InterruptedException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    // We use eclipse-temurin instead of openjdk due to its deprecation\n    // see https://hub.docker.com/_/openjdk#deprecation-notice\n    buildImage(\n        ImageReference.parse(\"eclipse-temurin:8-jre-alpine\"),\n        Containerizer.to(RegistryImage.named(dockerHost + \":5000/testimage:testtag\")),\n        Collections.emptyList());\n\n    String imageReference = dockerHost + \":5000/testimage:testtag\";\n    new Command(\"docker\", \"pull\", imageReference).run();\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\", new Command(\"docker\", \"run\", \"--rm\", imageReference).run());\n  }\n\n  @Test\n  public void testBuildToDockerDaemon_multipleTags()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    buildImage(\n        ImageReference.of(\"gcr.io\", \"distroless/java\", DISTROLESS_DIGEST),\n        Containerizer.to(DockerDaemonImage.named(\"testdocker\")),\n        Arrays.asList(\"testtag2\", \"testtag3\"));\n\n    progressChecker.checkCompletion();\n\n    assertLayerSize(7, \"testdocker\");\n    assertDockerInspect(\"testdocker\");\n    assertDockerInspect(\"testdocker:testtag2\");\n    assertDockerInspect(\"testdocker:testtag3\");\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\", new Command(\"docker\", \"run\", \"--rm\", \"testdocker\").run());\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", \"testdocker:testtag2\").run());\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", \"testdocker:testtag3\").run());\n  }\n\n  @Test\n  public void testBuildTarball()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    Path outputPath = temporaryFolder.newFolder().toPath().resolve(\"test.tar\");\n    buildImage(\n        ImageReference.of(\"gcr.io\", \"distroless/java\", DISTROLESS_DIGEST),\n        Containerizer.to(TarImage.at(outputPath).named(\"testtar\")),\n        Collections.emptyList());\n\n    progressChecker.checkCompletion();\n\n    new Command(\"docker\", \"load\", \"--input\", outputPath.toString()).run();\n    assertLayerSize(7, \"testtar\");\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\", new Command(\"docker\", \"run\", \"--rm\", \"testtar\").run());\n  }\n\n  private JibContainer buildImage(\n      ImageReference baseImage, Containerizer containerizer, List<String> additionalTags)\n      throws IOException, InterruptedException, RegistryException, CacheDirectoryCreationException,\n          ExecutionException {\n    JibContainerBuilder containerBuilder =\n        Jib.from(baseImage)\n            .setEntrypoint(\n                Arrays.asList(\n                    \"java\", \"-cp\", \"/app/resources:/app/classes:/app/libs/*\", \"HelloWorld\"))\n            .setProgramArguments(Collections.singletonList(\"An argument.\"))\n            .setEnvironment(ImmutableMap.of(\"env1\", \"envvalue1\", \"env2\", \"envvalue2\"))\n            .setExposedPorts(Ports.parse(Arrays.asList(\"1000\", \"2000-2002/tcp\", \"3000/udp\")))\n            .setLabels(ImmutableMap.of(\"key1\", \"value1\", \"key2\", \"value2\"))\n            .setFileEntriesLayers(fakeLayerConfigurations);\n\n    containerizer\n        .setAllowInsecureRegistries(true)\n        .setToolName(\"jib-integration-test\")\n        .addEventHandler(ProgressEvent.class, progressChecker.progressEventHandler);\n    additionalTags.forEach(containerizer::withAdditionalTag);\n\n    return containerBuilder.containerize(containerizer);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.LocalRegistry;\nimport com.google.cloud.tools.jib.registry.ManifestPullerIntegrationTest;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Integration tests for {@link Jib}. */\npublic class JibIntegrationTest {\n\n  /** A known oci index sha for gcr.io/distroless/base. */\n  public static final String KNOWN_OCI_INDEX_SHA =\n      \"sha256:2c50b819aa3bfaf6ae72e47682f6c5abc0f647cf3f4224a4a9be97dd30433909\";\n\n  @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private String imageToDelete;\n\n  private final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  private final RegistryClient registryClient =\n      RegistryClient.factory(\n              EventHandlers.NONE,\n              dockerHost + \":5000\",\n              \"jib-scratch\",\n              new FailoverHttpClient(true, true, ignored -> {}))\n          .newRegistryClient();\n\n  private final RegistryClient distrolessRegistryClient =\n      RegistryClient.factory(\n              EventHandlers.NONE,\n              dockerHost + \":5000\",\n              \"jib-distroless\",\n              new FailoverHttpClient(true, true, ignored -> {}))\n          .newRegistryClient();\n\n  /**\n   * Pulls a built image and attempts to run it.\n   *\n   * @param imageReference the image reference of the built image\n   * @return the container output\n   * @throws IOException if an I/O exception occurs\n   * @throws InterruptedException if the process was interrupted\n   */\n  private static String pullAndRunBuiltImage(String imageReference)\n      throws IOException, InterruptedException {\n    localRegistry.pull(imageReference);\n    return new Command(\"docker\", \"run\", \"--rm\", imageReference).run();\n  }\n\n  @BeforeClass\n  public static void setUpClass() throws IOException, InterruptedException {\n    localRegistry.pullAndPushToLocal(\"busybox\", \"busybox\");\n  }\n\n  @Before\n  public void setUp() {\n    System.setProperty(\"sendCredentialsOverHttp\", \"true\");\n  }\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    System.clearProperty(\"sendCredentialsOverHttp\");\n    if (imageToDelete != null) {\n      new Command(\"docker\", \"rmi\", imageToDelete).run();\n    }\n  }\n\n  @Test\n  public void testBasic_helloWorld()\n      throws InvalidImageReferenceException, InterruptedException, CacheDirectoryCreationException,\n          IOException, RegistryException, ExecutionException {\n    String toImage = dockerHost + \":5000/basic-helloworld\";\n    JibContainer jibContainer =\n        Jib.from(dockerHost + \":5000/busybox\")\n            .setEntrypoint(\"echo\", \"Hello World\")\n            .containerize(\n                Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    Assert.assertEquals(\"Hello World\\n\", pullAndRunBuiltImage(toImage));\n    Assert.assertEquals(\n        \"Hello World\\n\", pullAndRunBuiltImage(toImage + \"@\" + jibContainer.getDigest()));\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testBasic_dockerDaemonBaseImage()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    String toImage = dockerHost + \":5000/basic-dockerdaemon\";\n    JibContainer jibContainer =\n        Jib.from(\"docker://\" + dockerHost + \":5000/busybox\")\n            .setEntrypoint(\"echo\", \"Hello World\")\n            .containerize(\n                Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    Assert.assertEquals(\"Hello World\\n\", pullAndRunBuiltImage(toImage));\n    Assert.assertEquals(\n        \"Hello World\\n\", pullAndRunBuiltImage(toImage + \"@\" + jibContainer.getDigest()));\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testBasic_dockerDaemonBaseImageToDockerDaemon()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    String toImage = dockerHost + \":5000/docker-to-docker\";\n    Jib.from(DockerDaemonImage.named(dockerHost + \":5000/busybox\"))\n        .setEntrypoint(\"echo\", \"Hello World\")\n        .containerize(Containerizer.to(DockerDaemonImage.named(toImage)));\n\n    String output = new Command(\"docker\", \"run\", \"--rm\", toImage).run();\n    Assert.assertEquals(\"Hello World\\n\", output);\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testBasic_tarBaseImage_dockerSavedCommand()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    Path path = temporaryFolder.getRoot().toPath().resolve(\"docker-save.tar\");\n    new Command(\"docker\", \"save\", dockerHost + \":5000/busybox\", \"-o=\" + path).run();\n\n    String toImage = dockerHost + \":5000/basic-dockersavedcommand\";\n    JibContainer jibContainer =\n        Jib.from(\"tar://\" + path)\n            .setEntrypoint(\"echo\", \"Hello World\")\n            .containerize(\n                Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    Assert.assertEquals(\"Hello World\\n\", pullAndRunBuiltImage(toImage));\n    Assert.assertEquals(\n        \"Hello World\\n\", pullAndRunBuiltImage(toImage + \"@\" + jibContainer.getDigest()));\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testBasic_tarBaseImage_dockerSavedFile()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException, URISyntaxException {\n    // tar saved with 'docker save busybox -o busybox.tar'\n    Path path = Paths.get(Resources.getResource(\"core/busybox-docker.tar\").toURI());\n\n    String toImage = dockerHost + \":5000/basic-dockersavedfile\";\n    JibContainer jibContainer =\n        Jib.from(TarImage.at(path).named(\"ignored\"))\n            .setEntrypoint(\"echo\", \"Hello World\")\n            .containerize(\n                Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    Assert.assertEquals(\"Hello World\\n\", pullAndRunBuiltImage(toImage));\n    Assert.assertEquals(\n        \"Hello World\\n\", pullAndRunBuiltImage(toImage + \"@\" + jibContainer.getDigest()));\n  }\n\n  @Test\n  public void testBasic_tarBaseImage_jibImage()\n      throws InvalidImageReferenceException, InterruptedException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException, IOException, URISyntaxException {\n    Path outputPath = temporaryFolder.getRoot().toPath().resolve(\"jib-image.tar\");\n    Jib.from(dockerHost + \":5000/busybox\")\n        .addLayer(\n            Collections.singletonList(Paths.get(Resources.getResource(\"core/hello\").toURI())), \"/\")\n        .containerize(\n            Containerizer.to(TarImage.at(outputPath).named(\"ignored\"))\n                .setAllowInsecureRegistries(true));\n\n    String toImage = dockerHost + \":5000/basic-jibtar\";\n    JibContainer jibContainer =\n        Jib.from(TarImage.at(outputPath).named(\"ignored\"))\n            .setEntrypoint(\"cat\", \"/hello\")\n            .containerize(\n                Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    Assert.assertEquals(\"Hello World\\n\", pullAndRunBuiltImage(toImage));\n    Assert.assertEquals(\n        \"Hello World\\n\", pullAndRunBuiltImage(toImage + \"@\" + jibContainer.getDigest()));\n  }\n\n  @Test\n  public void testBasic_tarBaseImage_jibImageToDockerDaemon()\n      throws InvalidImageReferenceException, InterruptedException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException, IOException, URISyntaxException {\n    // tar saved with Jib.from(\"busybox\").addLayer(...(\"core/hello\")).containerize(TarImage.at...)\n    Path path = Paths.get(Resources.getResource(\"core/busybox-jib.tar\").toURI());\n\n    String toImage = dockerHost + \":5000/basic-jibtar-to-docker\";\n    JibContainer jibContainer =\n        Jib.from(TarImage.at(path).named(\"ignored\"))\n            .setEntrypoint(\"cat\", \"/hello\")\n            .containerize(\n                Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    Assert.assertEquals(\"Hello World\\n\", pullAndRunBuiltImage(toImage));\n    Assert.assertEquals(\n        \"Hello World\\n\", pullAndRunBuiltImage(toImage + \"@\" + jibContainer.getDigest()));\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testScratch_defaultPlatform()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    Jib.fromScratch()\n        .containerize(\n            Containerizer.to(RegistryImage.named(dockerHost + \":5000/jib-scratch:default-platform\"))\n                .setAllowInsecureRegistries(true));\n\n    V22ManifestTemplate manifestTemplate =\n        registryClient.pullManifest(\"default-platform\", V22ManifestTemplate.class).getManifest();\n    String containerConfig =\n        Blobs.writeToString(\n            registryClient.pullBlob(\n                manifestTemplate.getContainerConfiguration().getDigest(),\n                ignored -> {},\n                ignored -> {}));\n\n    Assert.assertTrue(manifestTemplate.getLayers().isEmpty());\n    Assert.assertTrue(containerConfig.contains(\"\\\"architecture\\\":\\\"amd64\\\"\"));\n    Assert.assertTrue(containerConfig.contains(\"\\\"os\\\":\\\"linux\\\"\"));\n  }\n\n  @Test\n  public void testScratch_singlePlatform()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    Jib.fromScratch()\n        .setPlatforms(ImmutableSet.of(new Platform(\"arm64\", \"windows\")))\n        .containerize(\n            Containerizer.to(RegistryImage.named(dockerHost + \":5000/jib-scratch:single-platform\"))\n                .setAllowInsecureRegistries(true));\n\n    V22ManifestTemplate manifestTemplate =\n        registryClient.pullManifest(\"single-platform\", V22ManifestTemplate.class).getManifest();\n    String containerConfig =\n        Blobs.writeToString(\n            registryClient.pullBlob(\n                manifestTemplate.getContainerConfiguration().getDigest(),\n                ignored -> {},\n                ignored -> {}));\n\n    Assert.assertTrue(manifestTemplate.getLayers().isEmpty());\n    Assert.assertTrue(containerConfig.contains(\"\\\"architecture\\\":\\\"arm64\\\"\"));\n    Assert.assertTrue(containerConfig.contains(\"\\\"os\\\":\\\"windows\\\"\"));\n  }\n\n  @Test\n  public void testScratch_multiPlatform()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    Jib.fromScratch()\n        .setPlatforms(\n            ImmutableSet.of(new Platform(\"arm64\", \"windows\"), new Platform(\"amd32\", \"windows\")))\n        .containerize(\n            Containerizer.to(RegistryImage.named(dockerHost + \":5000/jib-scratch:multi-platform\"))\n                .setAllowInsecureRegistries(true));\n\n    V22ManifestListTemplate manifestList =\n        (V22ManifestListTemplate) registryClient.pullManifest(\"multi-platform\").getManifest();\n    Assert.assertEquals(2, manifestList.getManifests().size());\n    ManifestDescriptorTemplate.Platform platform1 =\n        manifestList.getManifests().get(0).getPlatform();\n    ManifestDescriptorTemplate.Platform platform2 =\n        manifestList.getManifests().get(1).getPlatform();\n\n    Assert.assertEquals(\"arm64\", platform1.getArchitecture());\n    Assert.assertEquals(\"windows\", platform1.getOs());\n    Assert.assertEquals(\"amd32\", platform2.getArchitecture());\n    Assert.assertEquals(\"windows\", platform2.getOs());\n  }\n\n  @Test\n  public void testBasic_jibImageToDockerDaemon()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    String toImage = dockerHost + \":5000/docker-to-docker\";\n    Jib.from(DockerDaemonImage.named(dockerHost + \":5000/busybox\"))\n        .setEntrypoint(\"echo\", \"Hello World\")\n        .containerize(Containerizer.to(DockerDaemonImage.named(toImage)));\n\n    String output = new Command(\"docker\", \"run\", \"--rm\", toImage).run();\n    Assert.assertEquals(\"Hello World\\n\", output);\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testDistroless_ociManifest()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    Jib.from(\"gcr.io/distroless/base@\" + KNOWN_OCI_INDEX_SHA)\n        .setPlatforms(\n            ImmutableSet.of(new Platform(\"arm64\", \"linux\"), new Platform(\"amd64\", \"linux\")))\n        .containerize(\n            Containerizer.to(\n                    RegistryImage.named(dockerHost + \":5000/jib-distroless:multi-platform\"))\n                .setAllowInsecureRegistries(true));\n\n    V22ManifestListTemplate manifestList =\n        (V22ManifestListTemplate)\n            distrolessRegistryClient.pullManifest(\"multi-platform\").getManifest();\n    Assert.assertEquals(2, manifestList.getManifests().size());\n    ManifestDescriptorTemplate.Platform platform1 =\n        manifestList.getManifests().get(0).getPlatform();\n    ManifestDescriptorTemplate.Platform platform2 =\n        manifestList.getManifests().get(1).getPlatform();\n\n    Assert.assertEquals(\"arm64\", platform1.getArchitecture());\n    Assert.assertEquals(\"linux\", platform1.getOs());\n    Assert.assertEquals(\"amd64\", platform2.getArchitecture());\n    Assert.assertEquals(\"linux\", platform2.getOs());\n  }\n\n  @Test\n  public void testOffline()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    Path cacheDirectory = temporaryFolder.getRoot().toPath();\n\n    JibContainerBuilder jibContainerBuilder =\n        Jib.from(dockerHost + \":5000/busybox\").setEntrypoint(\"echo\", \"Hello World\");\n\n    // Should fail since Jib can't build to registry offline\n    try {\n      jibContainerBuilder.containerize(\n          Containerizer.to(RegistryImage.named(\"ignored\")).setOfflineMode(true));\n      Assert.fail();\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\"Cannot build to a container registry in offline mode\", ex.getMessage());\n    }\n\n    // Should fail since Jib hasn't cached the base image yet\n    try {\n      jibContainerBuilder.containerize(\n          Containerizer.to(DockerDaemonImage.named(\"ignored\"))\n              .setBaseImageLayersCache(cacheDirectory)\n              .setOfflineMode(true));\n      Assert.fail();\n    } catch (ExecutionException ex) {\n      Assert.assertEquals(\n          \"Cannot run Jib in offline mode; \"\n              + dockerHost\n              + \":5000/busybox not found in local Jib cache\",\n          ex.getCause().getMessage());\n    }\n\n    // Run online to cache the base image\n    jibContainerBuilder.containerize(\n        Containerizer.to(DockerDaemonImage.named(\"ignored\"))\n            .setBaseImageLayersCache(cacheDirectory)\n            .setAllowInsecureRegistries(true));\n\n    // Run again in offline mode, should succeed this time\n    jibContainerBuilder.containerize(\n        Containerizer.to(DockerDaemonImage.named(dockerHost + \":5000/offline\"))\n            .setBaseImageLayersCache(cacheDirectory)\n            .setOfflineMode(true));\n\n    // Verify output\n    Assert.assertEquals(\n        \"Hello World\\n\", new Command(\"docker\", \"run\", \"--rm\", dockerHost + \":5000/offline\").run());\n  }\n\n  /** Ensure that a provided executor is not disposed. */\n  @Test\n  public void testProvidedExecutorNotDisposed()\n      throws InvalidImageReferenceException, InterruptedException, CacheDirectoryCreationException,\n          IOException, RegistryException, ExecutionException {\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    try {\n      Jib.fromScratch()\n          .containerize(\n              Containerizer.to(RegistryImage.named(dockerHost + \":5000/foo\"))\n                  .setExecutorService(executorService)\n                  .setAllowInsecureRegistries(true));\n      Assert.assertFalse(executorService.isShutdown());\n    } finally {\n      executorService.shutdown();\n    }\n  }\n\n  @Test\n  public void testManifestListReferenceByShaDoesNotFail()\n      throws InvalidImageReferenceException, IOException, InterruptedException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    Containerizer containerizer =\n        Containerizer.to(TarImage.at(temporaryFolder.newFile(\"goose\").toPath()).named(\"whatever\"));\n\n    Jib.from(\"gcr.io/distroless/base@\" + ManifestPullerIntegrationTest.KNOWN_MANIFEST_LIST_SHA)\n        .containerize(containerizer);\n    // pass, no exceptions thrown\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibMultiPlatformIntegrationTest.java",
    "content": "/*\n * Copyright 2024 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.registry.LocalRegistry;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\npublic class JibMultiPlatformIntegrationTest {\n\n  @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);\n\n  private final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n  private String imageToDelete;\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    System.clearProperty(\"sendCredentialsOverHttp\");\n    if (imageToDelete != null) {\n      new Command(\"docker\", \"rmi\", imageToDelete).run();\n    }\n  }\n\n  @Test\n  public void testBasic_jibImageToDockerDaemon_arm64()\n      throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,\n          RegistryException, CacheDirectoryCreationException {\n    // Use arm64v8/busybox as base image.\n    String toImage = dockerHost + \":5000/docker-daemon-mismatched-arch\";\n    Jib.from(\n            RegistryImage.named(\n                \"busybox@sha256:eb427d855f82782c110b48b9a398556c629ce4951ae252c6f6751a136e194668\"))\n        .containerize(Containerizer.to(DockerDaemonImage.named(toImage)));\n    String os =\n        new Command(\"docker\", \"inspect\", toImage, \"--format\", \"{{.Os}}\").run().replace(\"\\n\", \"\");\n    String architecture =\n        new Command(\"docker\", \"inspect\", toImage, \"--format\", \"{{.Architecture}}\")\n            .run()\n            .replace(\"\\n\", \"\");\n    assertThat(os).isEqualTo(\"linux\");\n    assertThat(architecture).isEqualTo(\"arm64\");\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testBasicMultiPlatform_toDockerDaemon_pickFirstPlatformWhenNoMatchingImage()\n      throws IOException, InterruptedException, InvalidImageReferenceException,\n          CacheDirectoryCreationException, ExecutionException, RegistryException {\n    String toImage = dockerHost + \":5000/docker-daemon-multi-plat-mismatched-configs\";\n    Jib.from(\n            RegistryImage.named(\n                \"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977\"))\n        .setPlatforms(ImmutableSet.of(new Platform(\"s390x\", \"linux\"), new Platform(\"arm\", \"linux\")))\n        .containerize(\n            Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true));\n    String os =\n        new Command(\"docker\", \"inspect\", toImage, \"--format\", \"{{.Os}}\").run().replace(\"\\n\", \"\");\n    String architecture =\n        new Command(\"docker\", \"inspect\", toImage, \"--format\", \"{{.Architecture}}\")\n            .run()\n            .replace(\"\\n\", \"\");\n    assertThat(os).isEqualTo(\"linux\");\n    assertThat(architecture).isEqualTo(\"s390x\");\n    imageToDelete = toImage;\n  }\n\n  @Test\n  public void testBasicMultiPlatform_toDockerDaemon()\n      throws IOException, InterruptedException, ExecutionException, RegistryException,\n          CacheDirectoryCreationException, InvalidImageReferenceException {\n    String toImage = dockerHost + \":5000/docker-daemon-multi-platform\";\n    Jib.from(\n            RegistryImage.named(\n                \"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977\"))\n        .setPlatforms(\n            ImmutableSet.of(new Platform(\"arm64\", \"linux\"), new Platform(\"amd64\", \"linux\")))\n        .setEntrypoint(\"echo\", \"Hello World\")\n        .containerize(\n            Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true));\n\n    String output = new Command(\"docker\", \"run\", \"--rm\", toImage).run();\n    Assert.assertEquals(\"Hello World\\n\", output);\n    imageToDelete = toImage;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.common.collect.ArrayListMultimap;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Multimap;\nimport com.google.common.io.CharStreams;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.BiConsumer;\nimport java.util.zip.GZIPInputStream;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.junit.BeforeClass;\nimport org.junit.ClassRule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/**\n * Verify that created image has explicit directory structures, default timestamps, permissions, and\n * file orderings.\n */\npublic class ReproducibleImageTest {\n\n  @ClassRule public static final TemporaryFolder imageLocation = new TemporaryFolder();\n\n  private static File imageTar;\n\n  @BeforeClass\n  public static void createImage()\n      throws InvalidImageReferenceException, InterruptedException, CacheDirectoryCreationException,\n          IOException, RegistryException, ExecutionException {\n\n    Path root = imageLocation.getRoot().toPath();\n    Path fileA = Files.createFile(root.resolve(\"fileA.txt\"));\n    Path fileB = Files.createFile(root.resolve(\"fileB.txt\"));\n    Path fileC = Files.createFile(root.resolve(\"fileC.txt\"));\n    Path subdir = Files.createDirectory(root.resolve(\"dir\"));\n    Path subsubdir = Files.createDirectory(subdir.resolve(\"subdir\"));\n    Files.createFile(subdir.resolve(\"fileD.txt\"));\n    Files.createFile(subsubdir.resolve(\"fileE.txt\"));\n\n    imageTar = new File(imageLocation.getRoot(), \"image.tar\");\n    Containerizer containerizer =\n        Containerizer.to(TarImage.at(imageTar.toPath()).named(\"jib-core/reproducible\"));\n\n    Jib.fromScratch()\n        .setEntrypoint(\"echo\", \"Hello World\")\n        .addLayer(ImmutableList.of(fileA), AbsoluteUnixPath.get(\"/app\"))\n        // layer with out-of-order files\n        .addLayer(ImmutableList.of(fileC, fileB), \"/app\")\n        .addFileEntriesLayer(\n            FileEntriesLayer.builder()\n                .addEntryRecursive(subdir, AbsoluteUnixPath.get(\"/app\"))\n                .build())\n        .containerize(containerizer);\n  }\n\n  @Test\n  public void testTarballStructure() throws IOException {\n    // known content should produce known results\n    List<String> actual = new ArrayList<>();\n    try (TarArchiveInputStream input =\n        new TarArchiveInputStream(Files.newInputStream(imageTar.toPath()))) {\n      TarArchiveEntry imageEntry;\n      while ((imageEntry = input.getNextEntry()) != null) {\n        actual.add(imageEntry.getName());\n      }\n    }\n\n    assertThat(actual)\n        .containsExactly(\n            \"98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz\",\n            \"527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz\",\n            \"16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz\",\n            \"config.json\",\n            \"manifest.json\")\n        .inOrder();\n  }\n\n  @Test\n  public void testManifest() throws IOException {\n    String expectedManifest =\n        \"[{\\\"Config\\\":\\\"config.json\\\",\\\"RepoTags\\\":[\\\"jib-core/reproducible:latest\\\"],\"\n            + \"\\\"Layers\\\":[\\\"98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz\\\",\\\"527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz\\\",\\\"16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz\\\"]}]\";\n    String generatedManifest = extractFromTarFileAsString(imageTar, \"manifest.json\");\n    assertThat(generatedManifest).isEqualTo(expectedManifest);\n  }\n\n  @Test\n  public void testConfiguration() throws IOException {\n    String expectedConfig =\n        \"{\\\"created\\\":\\\"1970-01-01T00:00:00Z\\\",\\\"architecture\\\":\\\"amd64\\\",\\\"os\\\":\\\"linux\\\",\"\n            + \"\\\"config\\\":{\\\"Env\\\":[],\\\"Entrypoint\\\":[\\\"echo\\\",\\\"Hello World\\\"],\\\"ExposedPorts\\\":{},\\\"Labels\\\":{},\\\"Volumes\\\":{}},\"\n            + \"\\\"history\\\":[{\\\"created\\\":\\\"1970-01-01T00:00:00Z\\\",\\\"author\\\":\\\"Jib\\\",\\\"created_by\\\":\\\"jib-core:null\\\",\\\"comment\\\":\\\"\\\"},{\\\"created\\\":\\\"1970-01-01T00:00:00Z\\\",\\\"author\\\":\\\"Jib\\\",\\\"created_by\\\":\\\"jib-core:null\\\",\\\"comment\\\":\\\"\\\"},{\\\"created\\\":\\\"1970-01-01T00:00:00Z\\\",\\\"author\\\":\\\"Jib\\\",\\\"created_by\\\":\\\"jib-core:null\\\",\\\"comment\\\":\\\"\\\"}],\"\n            + \"\\\"rootfs\\\":{\\\"type\\\":\\\"layers\\\",\\\"diff_ids\\\":[\\\"sha256:2fcc2157bf42c89195676ef6e973a96d7b018c9d30ba89db95e9e0722e1c8ef3\\\",\\\"sha256:21f521f3217067d277af37512a08c72281d90fdd02d7174db632c8c3a34403bd\\\",\\\"sha256:6beba018395265af5061864b7f4678e831eb2daebb1045487c641fc8b142e319\\\"]}}\";\n    String generatedConfig = extractFromTarFileAsString(imageTar, \"config.json\");\n    assertThat(generatedConfig).isEqualTo(expectedConfig);\n  }\n\n  @Test\n  public void testImageLayout() throws IOException {\n    Set<String> paths = new HashSet<>();\n    layerEntriesDo(\n        (layerName, layerEntry) -> {\n          if (layerEntry.isFile()) {\n            paths.add(layerEntry.getName());\n          }\n        });\n    assertThat(paths)\n        .containsExactly(\n            \"app/fileA.txt\",\n            \"app/fileB.txt\",\n            \"app/fileC.txt\",\n            \"app/fileD.txt\",\n            \"app/subdir/fileE.txt\");\n  }\n\n  @Test\n  public void testAllFileAndDirectories() throws IOException {\n    layerEntriesDo(\n        (layerName, layerEntry) ->\n            assertThat(layerEntry.isFile() || layerEntry.isDirectory()).isTrue());\n  }\n\n  @Test\n  public void testTimestampsEpochPlus1s() throws IOException {\n    layerEntriesDo(\n        (layerName, layerEntry) -> {\n          Instant modificationTime = layerEntry.getLastModifiedDate().toInstant();\n          assertThat(modificationTime).isEqualTo(Instant.ofEpochSecond(1));\n        });\n  }\n\n  @Test\n  public void testPermissions() throws IOException {\n    assertThat(FilePermissions.DEFAULT_FILE_PERMISSIONS.getPermissionBits()).isEqualTo(0644);\n    assertThat(FilePermissions.DEFAULT_FOLDER_PERMISSIONS.getPermissionBits()).isEqualTo(0755);\n    layerEntriesDo(\n        (layerName, layerEntry) -> {\n          if (layerEntry.isFile()) {\n            assertThat(layerEntry.getMode() & 0777).isEqualTo(0644);\n          } else if (layerEntry.isDirectory()) {\n            assertThat(layerEntry.getMode() & 0777).isEqualTo(0755);\n          }\n        });\n  }\n\n  @Test\n  public void testNoImplicitParentDirectories() throws IOException {\n    Set<String> directories = new HashSet<>();\n    layerEntriesDo(\n        (layerName, layerEntry) -> {\n          String entryPath = layerEntry.getName();\n          if (layerEntry.isDirectory()) {\n            assertThat(entryPath.endsWith(\"/\")).isTrue();\n            entryPath = entryPath.substring(0, entryPath.length() - 1);\n          }\n\n          int lastSlashPosition = entryPath.lastIndexOf('/');\n          String parent = entryPath.substring(0, Math.max(0, lastSlashPosition));\n          if (!parent.isEmpty()) {\n            assertThat(directories.contains(parent)).isTrue();\n          }\n          if (layerEntry.isDirectory()) {\n            directories.add(entryPath);\n          }\n        });\n  }\n\n  @Test\n  public void testFileOrdering() throws IOException {\n    Multimap<String, String> layerPaths = ArrayListMultimap.create();\n    layerEntriesDo((layerName, layerEntry) -> layerPaths.put(layerName, layerEntry.getName()));\n    for (Collection<String> paths : layerPaths.asMap().values()) {\n      List<String> sorted = new ArrayList<>(paths);\n      // ReproducibleLayerBuilder sorts by TarArchiveEntry::getName()\n      Collections.sort(sorted);\n      assertThat(paths).containsExactlyElementsIn(sorted).inOrder();\n    }\n  }\n\n  private void layerEntriesDo(BiConsumer<String, TarArchiveEntry> layerConsumer)\n      throws IOException {\n\n    try (TarArchiveInputStream input =\n        new TarArchiveInputStream(Files.newInputStream(imageTar.toPath()))) {\n      TarArchiveEntry imageEntry;\n      while ((imageEntry = input.getNextEntry()) != null) {\n        String imageEntryName = imageEntry.getName();\n        // assume all .tar.gz files are layers\n        if (imageEntry.isFile() && imageEntryName.endsWith(\".tar.gz\")) {\n          @SuppressWarnings(\"resource\") // must not close sub-streams\n          TarArchiveInputStream layer = new TarArchiveInputStream(new GZIPInputStream(input));\n          TarArchiveEntry layerEntry;\n          while ((layerEntry = layer.getNextEntry()) != null) {\n            layerConsumer.accept(imageEntryName, layerEntry);\n          }\n        }\n      }\n    }\n  }\n\n  private static String extractFromTarFileAsString(File tarFile, String filename)\n      throws IOException {\n    try (TarArchiveInputStream input =\n        new TarArchiveInputStream(Files.newInputStream(tarFile.toPath()))) {\n      TarArchiveEntry imageEntry;\n      while ((imageEntry = input.getNextEntry()) != null) {\n        if (filename.equals(imageEntry.getName())) {\n          return CharStreams.toString(new InputStreamReader(input, StandardCharsets.UTF_8));\n        }\n      }\n    }\n    throw new AssertionError(\"file not found: \" + filename);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BearerAuthenticationIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport java.io.IOException;\nimport org.junit.Test;\n\n/** Integration tests for bearer authentication. */\npublic class BearerAuthenticationIntegrationTest {\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(false, false, ignored -> {});\n\n  @Test\n  public void testGetRegistryAuthenticator() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(\n                EventHandlers.NONE, \"registry.hub.docker.com\", \"library/busybox\", httpClient)\n            .newRegistryClient();\n    // For public images, Docker Hub still requires bearer authentication (without credentials)\n    registryClient.doPullBearerAuth();\n    registryClient.pullManifest(\"latest\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobCheckerIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Integration tests for {@link BlobChecker}. */\npublic class BlobCheckerIntegrationTest {\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n\n  @Test\n  public void testCheck_exists() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n    V22ManifestTemplate manifestTemplate =\n        registryClient\n            .pullManifest(\n                ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class)\n            .getManifest();\n    DescriptorDigest blobDigest = manifestTemplate.getLayers().get(0).getDigest();\n\n    Assert.assertEquals(blobDigest, registryClient.checkBlob(blobDigest).get().getDigest());\n  }\n\n  @Test\n  public void testCheck_doesNotExist() throws IOException, RegistryException, DigestException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n    DescriptorDigest fakeBlobDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n\n    Assert.assertFalse(registryClient.checkBlob(fakeBlobDigest).isPresent());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPullerIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.common.io.ByteStreams;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport java.util.concurrent.atomic.LongAdder;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Integration tests for {@link BlobPuller}. */\npublic class BlobPullerIntegrationTest {\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n\n  @Test\n  public void testPull() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n    V22ManifestTemplate manifestTemplate =\n        registryClient\n            .pullManifest(\n                ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class)\n            .getManifest();\n\n    DescriptorDigest realDigest = manifestTemplate.getLayers().get(0).getDigest();\n\n    // Pulls a layer BLOB of the distroless/base image.\n    LongAdder totalByteCount = new LongAdder();\n    LongAdder expectedSize = new LongAdder();\n    Blob pulledBlob =\n        registryClient.pullBlob(\n            realDigest,\n            size -> {\n              Assert.assertEquals(0, expectedSize.sum());\n              expectedSize.add(size);\n            },\n            totalByteCount::add);\n    Assert.assertEquals(realDigest, pulledBlob.writeTo(ByteStreams.nullOutputStream()).getDigest());\n    Assert.assertTrue(expectedSize.sum() > 0);\n    Assert.assertEquals(expectedSize.sum(), totalByteCount.sum());\n  }\n\n  @Test\n  public void testPull_unknownBlob() throws IOException, DigestException {\n    DescriptorDigest nonexistentDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    try {\n      registryClient\n          .pullBlob(nonexistentDigest, ignored -> {}, ignored -> {})\n          .writeTo(ByteStreams.nullOutputStream());\n      Assert.fail(\"Trying to pull nonexistent blob should have errored\");\n\n    } catch (IOException ex) {\n      if (!(ex.getCause() instanceof RegistryErrorException)) {\n        throw ex;\n      }\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"pull BLOB for gcr.io/distroless/base with digest \" + nonexistentDigest));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPusherIntegrationTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for {@link BlobPusher}. */\npublic class BlobPusherIntegrationTest {\n\n  @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n  private final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  @Test\n  public void testPush() throws DigestException, IOException, RegistryException {\n    Blob testBlob = Blobs.from(\"crepecake\");\n    // Known digest for 'crepecake'\n    DescriptorDigest testBlobDigest =\n        DescriptorDigest.fromHash(\n            \"52a9e4d4ba4333ce593707f98564fee1e6d898db0d3602408c0b2a6a424d357c\");\n\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, dockerHost + \":5000\", \"testimage\", httpClient)\n            .newRegistryClient();\n    Assert.assertFalse(registryClient.pushBlob(testBlobDigest, testBlob, null, ignored -> {}));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/LocalRegistry.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.common.collect.Lists;\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileAttribute;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport javax.annotation.Nullable;\nimport org.junit.rules.ExternalResource;\nimport org.mindrot.jbcrypt.BCrypt;\n\n/** Runs a local registry. */\npublic class LocalRegistry extends ExternalResource {\n\n  private final String containerName = \"registry-\" + UUID.randomUUID();\n  public final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n  private final int port;\n  @Nullable private final String username;\n  @Nullable private final String password;\n\n  public LocalRegistry(int port) {\n    this(port, null, null);\n  }\n\n  public LocalRegistry(int port, String username, String password) {\n    this.port = port;\n    this.username = username;\n    this.password = password;\n  }\n\n  /** Starts the local registry. */\n  @Override\n  protected void before() throws IOException, InterruptedException {\n    start();\n  }\n\n  @Override\n  protected void after() {\n    stop();\n  }\n\n  /** Starts the registry. */\n  public void start() throws IOException, InterruptedException {\n    // Runs the Docker registry.\n    List<String> dockerTokens =\n        Lists.newArrayList(\n            \"docker\", \"run\", \"--rm\", \"-d\", \"-p\", port + \":5000\", \"--name\", containerName);\n    if (username != null && password != null) {\n      // Equivalent of \"$ htpasswd -nbB username password\".\n      // https://httpd.apache.org/docs/2.4/misc/password_encryptions.html\n      // BCrypt generates hashes using $2a$ algorithm (instead of $2y$ from docs), but this seems\n      // to work okay\n      String credentialString = username + \":\" + BCrypt.hashpw(password, BCrypt.gensalt());\n      FileAttribute<Set<PosixFilePermission>> attrs =\n          PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rwxr-xr-x\"));\n      Path tempFolder = Files.createTempDirectory(Paths.get(\"/tmp\"), \"\", attrs);\n      Files.write(\n          tempFolder.resolve(\"htpasswd\"), credentialString.getBytes(StandardCharsets.UTF_8));\n      String authenticationVolume = tempFolder + \":/auth\";\n      if (System.getenv(\"KOKORO_JOB_CLUSTER\") != null\n          && System.getenv(\"KOKORO_JOB_CLUSTER\").equals(\"MACOS_EXTERNAL\")) {\n        authenticationVolume = \"/home/docker/auth:/auth\";\n      } else if (System.getenv(\"KOKORO_JOB_CLUSTER\") != null\n          && System.getenv(\"KOKORO_JOB_CLUSTER\").equals(\"GCP_UBUNTU_DOCKER\")) {\n        authenticationVolume = \"/tmpfs/auth:/auth\";\n      }\n      // Run the Docker registry\n      dockerTokens.addAll(\n          Arrays.asList(\n              \"-v\",\n              // Volume mount used for storing credentials\n              authenticationVolume,\n              \"-e\",\n              \"REGISTRY_AUTH=htpasswd\",\n              \"-e\",\n              \"REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm\",\n              \"-e\",\n              \"REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd\"));\n    }\n    dockerTokens.add(\"registry:2\");\n    new Command(dockerTokens).run();\n    waitUntilReady();\n  }\n\n  /** Stops the registry. */\n  public void stop() {\n    try {\n      logout();\n      new Command(\"docker\", \"stop\", containerName).run();\n\n    } catch (InterruptedException | IOException ex) {\n      throw new RuntimeException(\"Could not stop local registry fully: \" + containerName, ex);\n    }\n  }\n\n  /**\n   * Pulls an image to a Docker daemon (not to this local registry).\n   *\n   * @param from the image reference to pull\n   * @throws IOException if the pull command fails\n   * @throws InterruptedException if the pull command is interrupted\n   */\n  public void pull(String from) throws IOException, InterruptedException {\n    login();\n    new Command(\"docker\", \"pull\", from).run();\n    logout();\n  }\n\n  /**\n   * Pulls an image and pushes it to the local registry under a new tag.\n   *\n   * @param from the image reference to pull\n   * @param to the new location of the image (i.e. {@code localhost:[port]/[to]}\n   * @throws IOException if the commands fail\n   * @throws InterruptedException if the commands are interrupted\n   */\n  public void pullAndPushToLocal(String from, String to) throws IOException, InterruptedException {\n    login();\n    new Command(\"docker\", \"pull\", from).run();\n    new Command(\"docker\", \"tag\", from, dockerHost + \":\" + port + \"/\" + to).run();\n    new Command(\"docker\", \"push\", dockerHost + \":\" + port + \"/\" + to).run();\n    logout();\n  }\n\n  private void login() throws IOException, InterruptedException {\n    if (username != null && password != null) {\n      new Command(\"docker\", \"login\", dockerHost + \":\" + port, \"-u\", username, \"--password-stdin\")\n          .run(password.getBytes(StandardCharsets.UTF_8));\n    }\n  }\n\n  private void logout() throws IOException, InterruptedException {\n    if (username != null && password != null) {\n      new Command(\"docker\", \"logout\", dockerHost + \":\" + port).run();\n    }\n  }\n\n  private void waitUntilReady() throws InterruptedException, MalformedURLException {\n    URL queryUrl = new URL(\"http://\" + dockerHost + \":\" + port + \"/v2/_catalog\");\n\n    for (int i = 0; i < 40; i++) {\n      try {\n        HttpURLConnection connection = (HttpURLConnection) queryUrl.openConnection();\n        int code = connection.getResponseCode();\n        if (code == HttpURLConnection.HTTP_OK || code == HttpURLConnection.HTTP_UNAUTHORIZED) {\n          return;\n        }\n      } catch (IOException ex) {\n        // ignored\n      }\n      Thread.sleep(250);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestCheckerIntegrationTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport java.io.IOException;\nimport java.util.Optional;\nimport org.junit.Test;\n\n/** Integration tests for {@link ManifestChecker}. */\npublic class ManifestCheckerIntegrationTest {\n\n  /** A known manifest list sha for gcr.io/distroless/base. */\n  private static final String KNOWN_MANIFEST =\n      \"sha256:44cbdb9c24e123882d7894ba78fb6f572d2496889885a47eb4b32241a8c07a00\";\n\n  /** A fictitious sha to test unknown images. */\n  private static final String UNKNOWN_MANIFEST =\n      \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\";\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n\n  @Test\n  public void testExistingManifest() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    Optional<ManifestAndDigest<ManifestTemplate>> manifestDescriptor =\n        registryClient.checkManifest(KNOWN_MANIFEST);\n\n    assertTrue(manifestDescriptor.isPresent());\n    assertEquals(KNOWN_MANIFEST, manifestDescriptor.get().getDigest().toString());\n  }\n\n  @Test\n  public void testNonExistingManifest() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    Optional<ManifestAndDigest<ManifestTemplate>> manifestDescriptor =\n        registryClient.checkManifest(UNKNOWN_MANIFEST);\n\n    assertEquals(Optional.empty(), manifestDescriptor);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPullerIntegrationTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport java.io.IOException;\nimport org.junit.Assert;\nimport org.junit.BeforeClass;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for {@link ManifestPuller}. */\npublic class ManifestPullerIntegrationTest {\n\n  /** A known manifest list sha for gcr.io/distroless/base. */\n  public static final String KNOWN_MANIFEST_LIST_SHA =\n      \"sha256:44cbdb9c24e123882d7894ba78fb6f572d2496889885a47eb4b32241a8c07a00\";\n\n  /** A known OCI image index sha for gcr.io/distroless/base. */\n  public static final String KNOWN_OCI_INDEX_SHA =\n      \"sha256:2c50b819aa3bfaf6ae72e47682f6c5abc0f647cf3f4224a4a9be97dd30433909\";\n\n  /** A known docker manifest schema 2 sha for gcr.io/distroless/base. */\n  public static final String KNOWN_MANIFEST_V22_SHA =\n      \"sha256:da5c568e59f3241b09e5699a525a37b3309ce2c182d8d20802b9eaee55711b19\";\n\n  /** A known oci manifest sha for gcr.io/distroless/base. */\n  public static final String KNOWN_OCI_MANIFEST_SHA =\n      \"sha256:0477dc38b254096e350a9b605b7355d3cf0d5a844558e6986148ce2a1fe18ba8\";\n\n  @ClassRule public static LocalRegistry localRegistry = new LocalRegistry(5000);\n  public final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  @BeforeClass\n  public static void setUp() throws IOException, InterruptedException {\n    localRegistry.pullAndPushToLocal(\"busybox\", \"busybox\");\n  }\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n\n  @Test\n  public void testPull_v21() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, dockerHost + \":5000\", \"busybox\", httpClient)\n            .newRegistryClient();\n\n    V21ManifestTemplate manifestTemplate =\n        registryClient.pullManifest(\"latest\", V21ManifestTemplate.class).getManifest();\n\n    assertThat(manifestTemplate.getSchemaVersion()).isEqualTo(1);\n    assertThat(manifestTemplate.getFsLayers()).isNotEmpty();\n  }\n\n  @Test\n  public void testPull_v22() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    V22ManifestTemplate manifestTemplate =\n        registryClient\n            .pullManifest(KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class)\n            .getManifest();\n\n    assertThat(manifestTemplate.getSchemaVersion()).isEqualTo(2);\n    assertThat(manifestTemplate.getLayers()).isNotEmpty();\n  }\n\n  @Test\n  public void testPull_ociManifest() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    OciManifestTemplate manifestTemplate =\n        registryClient\n            .pullManifest(KNOWN_OCI_MANIFEST_SHA, OciManifestTemplate.class)\n            .getManifest();\n\n    assertThat(manifestTemplate.getSchemaVersion()).isEqualTo(2);\n    assertThat(manifestTemplate.getLayers()).isNotEmpty();\n  }\n\n  @Test\n  public void testPull_v22ManifestList() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    // Ensures call to image at the specified SHA returns a manifest list\n    V22ManifestListTemplate manifestListTargeted =\n        registryClient\n            .pullManifest(KNOWN_MANIFEST_LIST_SHA, V22ManifestListTemplate.class)\n            .getManifest();\n    assertThat(manifestListTargeted.getSchemaVersion()).isEqualTo(2);\n    assertThat(manifestListTargeted.getManifests()).isNotEmpty();\n\n    // Generic call to image at the specified SHA, should also return a manifest list\n    ManifestTemplate manifestListGeneric =\n        registryClient.pullManifest(KNOWN_MANIFEST_LIST_SHA).getManifest();\n    assertThat(manifestListGeneric.getSchemaVersion()).isEqualTo(2);\n    assertThat(manifestListGeneric).isInstanceOf(V22ManifestListTemplate.class);\n    assertThat(((V22ManifestListTemplate) manifestListGeneric).getManifests()).isNotEmpty();\n  }\n\n  @Test\n  public void testPull_ociIndex() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/base\", httpClient)\n            .newRegistryClient();\n\n    // Ensures call to image at the specified SHA returns an OCI index\n    OciIndexTemplate manifestListTargeted =\n        registryClient.pullManifest(KNOWN_OCI_INDEX_SHA, OciIndexTemplate.class).getManifest();\n    assertThat(manifestListTargeted.getSchemaVersion()).isEqualTo(2);\n    assertThat((manifestListTargeted.getManifests().size() > 0)).isTrue();\n\n    // Generic call to image at the specified SHA, should also return an OCI index\n    ManifestTemplate manifestListGeneric =\n        registryClient.pullManifest(KNOWN_OCI_INDEX_SHA).getManifest();\n    assertThat(manifestListGeneric.getSchemaVersion()).isEqualTo(2);\n    assertThat(manifestListGeneric).isInstanceOf(OciIndexTemplate.class);\n    assertThat(((OciIndexTemplate) manifestListGeneric).getManifests()).isNotEmpty();\n  }\n\n  @Test\n  public void testPull_unknownManifest() throws RegistryException, IOException {\n    try {\n      RegistryClient registryClient =\n          RegistryClient.factory(EventHandlers.NONE, dockerHost + \":5000\", \"busybox\", httpClient)\n              .newRegistryClient();\n      registryClient.pullManifest(\"nonexistent-tag\");\n      Assert.fail(\"Trying to pull nonexistent image should have errored\");\n\n    } catch (RegistryErrorException ex) {\n      assertThat(ex)\n          .hasMessageThat()\n          .contains(\"pull image manifest for \" + dockerHost + \":5000/busybox:nonexistent-tag\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for {@link ManifestPusher}. */\npublic class ManifestPusherIntegrationTest {\n\n  @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);\n\n  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n  public final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  @Test\n  public void testPush_missingBlobs() throws IOException, RegistryException {\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, \"gcr.io\", \"distroless/java\", httpClient)\n            .newRegistryClient();\n    ManifestTemplate manifestTemplate = registryClient.pullManifest(\"latest\").getManifest();\n\n    registryClient =\n        RegistryClient.factory(EventHandlers.NONE, dockerHost + \":5000\", \"ignored\", httpClient)\n            .newRegistryClient();\n    try {\n      registryClient.pushManifest(manifestTemplate, \"latest\");\n      Assert.fail(\"Pushing manifest without its BLOBs should fail\");\n\n    } catch (RegistryErrorException ex) {\n      ResponseException responseException = (ResponseException) ex.getCause();\n      Assert.assertEquals(\n          HttpStatusCodes.STATUS_CODE_BAD_REQUEST, responseException.getStatusCode());\n    }\n  }\n\n  /** Tests manifest pushing. This test is a comprehensive test of push and pull. */\n  @Test\n  public void testPush() throws DigestException, IOException, RegistryException {\n    Blob testLayerBlob = Blobs.from(\"crepecake\");\n    // Known digest for 'crepecake'\n    DescriptorDigest testLayerBlobDigest =\n        DescriptorDigest.fromHash(\n            \"52a9e4d4ba4333ce593707f98564fee1e6d898db0d3602408c0b2a6a424d357c\");\n    Blob testContainerConfigurationBlob = Blobs.from(\"12345\");\n    DescriptorDigest testContainerConfigurationBlobDigest =\n        DescriptorDigest.fromHash(\n            \"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5\");\n\n    // Creates a valid image manifest.\n    V22ManifestTemplate expectedManifestTemplate = new V22ManifestTemplate();\n    expectedManifestTemplate.addLayer(9, testLayerBlobDigest);\n    expectedManifestTemplate.setContainerConfiguration(5, testContainerConfigurationBlobDigest);\n\n    // Pushes the BLOBs.\n    RegistryClient registryClient =\n        RegistryClient.factory(EventHandlers.NONE, dockerHost + \":5000\", \"testimage\", httpClient)\n            .newRegistryClient();\n    Assert.assertFalse(\n        registryClient.pushBlob(testLayerBlobDigest, testLayerBlob, null, ignored -> {}));\n    Assert.assertFalse(\n        registryClient.pushBlob(\n            testContainerConfigurationBlobDigest,\n            testContainerConfigurationBlob,\n            null,\n            ignored -> {}));\n\n    // Pushes the manifest.\n    DescriptorDigest imageDigest = registryClient.pushManifest(expectedManifestTemplate, \"latest\");\n\n    // Pulls the manifest.\n    V22ManifestTemplate manifestTemplate =\n        registryClient.pullManifest(\"latest\", V22ManifestTemplate.class).getManifest();\n    Assert.assertEquals(1, manifestTemplate.getLayers().size());\n    Assert.assertEquals(testLayerBlobDigest, manifestTemplate.getLayers().get(0).getDigest());\n    Assert.assertNotNull(manifestTemplate.getContainerConfiguration());\n    Assert.assertEquals(\n        testContainerConfigurationBlobDigest,\n        manifestTemplate.getContainerConfiguration().getDigest());\n\n    // Pulls the manifest by digest.\n    V22ManifestTemplate manifestTemplateByDigest =\n        registryClient\n            .pullManifest(imageDigest.toString(), V22ManifestTemplate.class)\n            .getManifest();\n    Assert.assertEquals(\n        Digests.computeJsonDigest(manifestTemplate),\n        Digests.computeJsonDigest(manifestTemplateByDigest));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelperIntegrationTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Integration tests for {@link DockerCredentialHelper}. */\npublic class DockerCredentialHelperIntegrationTest {\n\n  // This binary must exist and be functioning properly for these tests to succeed.\n  private static final String GCR_CREDENTIAL_HELPER = \"docker-credential-gcr\";\n\n  /** Tests retrieval via {@code docker-credential-gcr} CLI. */\n  @Test\n  public void testRetrieveGcr()\n      throws IOException, CredentialHelperUnhandledServerUrlException,\n          CredentialHelperNotFoundException, URISyntaxException, InterruptedException {\n    new Command(GCR_CREDENTIAL_HELPER, \"store\")\n        .run(Files.readAllBytes(Paths.get(Resources.getResource(\"credentials.json\").toURI())));\n\n    DockerCredentialHelper dockerCredentialHelper =\n        new DockerCredentialHelper(\"myregistry\", Paths.get(GCR_CREDENTIAL_HELPER));\n\n    Credential credentials = dockerCredentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credentials.getUsername());\n    Assert.assertEquals(\"mysecret\", credentials.getPassword());\n  }\n\n  @Test\n  public void testRetrieve_nonexistentCredentialHelper()\n      throws IOException, CredentialHelperUnhandledServerUrlException {\n    try {\n      DockerCredentialHelper fakeDockerCredentialHelper =\n          new DockerCredentialHelper(\"\", Paths.get(\"non-existing-helper\"));\n\n      fakeDockerCredentialHelper.retrieve();\n\n      Assert.fail(\"Retrieve should have failed for nonexistent credential helper\");\n\n    } catch (CredentialHelperNotFoundException ex) {\n      Assert.assertEquals(\"The system does not have non-existing-helper CLI\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRetrieve_nonexistentServerUrl()\n      throws IOException, CredentialHelperNotFoundException {\n    try {\n      DockerCredentialHelper fakeDockerCredentialHelper =\n          new DockerCredentialHelper(\"fake.server.url\", Paths.get(GCR_CREDENTIAL_HELPER));\n\n      fakeDockerCredentialHelper.retrieve();\n\n      Assert.fail(\"Retrieve should have failed for nonexistent server URL\");\n\n    } catch (CredentialHelperUnhandledServerUrlException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"The credential helper (docker-credential-gcr) has nothing for server URL: fake.server.url\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/integration-test/resources/core/hello",
    "content": "Hello World\n"
  },
  {
    "path": "jib-core/src/integration-test/resources/credentials.json",
    "content": "{\n  \"ServerURL\": \"myregistry\",\n  \"Username\": \"myusername\",\n  \"Secret\": \"mysecret\"\n}"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/ProjectInfo.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib;\n\n/** Constants relating to the Jib project. */\npublic class ProjectInfo {\n\n  /** Link to the GitHub repository. */\n  public static final String GITHUB_URL = \"https://github.com/GoogleContainerTools/jib\";\n\n  /** Link to file an issue against the GitHub repository. */\n  public static final String GITHUB_NEW_ISSUE_URL = GITHUB_URL + \"/issues/new\";\n\n  private ProjectInfo() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/CacheDirectoryCreationException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\n/** Thrown when a directory to be used as the cache could not be created. */\npublic class CacheDirectoryCreationException extends Exception {\n\n  private static final String MESSAGE = \"Could not create cache directory\";\n\n  public CacheDirectoryCreationException(Throwable cause) {\n    super(MESSAGE, cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/Containerizer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.builder.steps.BuildResult;\nimport com.google.cloud.tools.jib.builder.steps.StepsRunner;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport com.google.cloud.tools.jib.docker.DockerClientResolver;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.filesystem.XdgDirectories;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ArrayListMultimap;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.ListMultimap;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\n\n/** Configures how to containerize. */\npublic class Containerizer {\n\n  /**\n   * The default directory for caching the base image layers, in {@code [user cache\n   * home]/google-cloud-tools-java/jib}.\n   */\n  public static final Path DEFAULT_BASE_CACHE_DIRECTORY = XdgDirectories.getCacheHome();\n\n  public static final String DEFAULT_APPLICATION_CACHE_DIRECTORY_NAME =\n      \"jib-core-application-layers-cache\";\n\n  private static final String DEFAULT_TOOL_NAME = \"jib-core\";\n  private static final String DEFAULT_TOOL_VERSION =\n      Containerizer.class.getPackage().getImplementationVersion();\n\n  private static final String DESCRIPTION_FOR_DOCKER_REGISTRY = \"Building and pushing image\";\n  private static final String DESCRIPTION_FOR_DOCKER_DAEMON = \"Building image to Docker daemon\";\n  private static final String DESCRIPTION_FOR_TARBALL = \"Building image tarball\";\n\n  /**\n   * Gets a new {@link Containerizer} that containerizes to a container registry.\n   *\n   * @param registryImage the {@link RegistryImage} that defines target container registry and\n   *     credentials\n   * @return a new {@link Containerizer}\n   */\n  public static Containerizer to(RegistryImage registryImage) {\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(registryImage.getImageReference())\n            .setCredentialRetrievers(registryImage.getCredentialRetrievers())\n            .build();\n\n    Function<BuildContext, StepsRunner> stepsRunnerFactory =\n        buildContext -> StepsRunner.begin(buildContext).registryPushSteps();\n\n    return new Containerizer(\n        DESCRIPTION_FOR_DOCKER_REGISTRY, imageConfiguration, stepsRunnerFactory, true);\n  }\n\n  /**\n   * Gets a new {@link Containerizer} that containerizes to a Docker daemon.\n   *\n   * @param dockerDaemonImage the {@link DockerDaemonImage} that defines target Docker daemon\n   * @return a new {@link Containerizer}\n   */\n  public static Containerizer to(DockerDaemonImage dockerDaemonImage) {\n    DockerClient dockerClient =\n        DockerClientResolver.resolve(dockerDaemonImage.getDockerEnvironment())\n            .orElse(\n                new CliDockerClient(\n                    dockerDaemonImage.getDockerExecutable(),\n                    dockerDaemonImage.getDockerEnvironment()));\n\n    return to(dockerClient, dockerDaemonImage);\n  }\n\n  /**\n   * Gets a new {@link Containerizer} that containerizes to a tarball archive.\n   *\n   * @param tarImage the {@link TarImage} that defines target output file\n   * @return a new {@link Containerizer}\n   */\n  public static Containerizer to(TarImage tarImage) {\n    Optional<ImageReference> imageReference = tarImage.getImageReference();\n    if (!imageReference.isPresent()) {\n      throw new IllegalArgumentException(\n          \"Image name must be set when building a TarImage; use TarImage#named(...) to set the name\"\n              + \" of the target image\");\n    }\n\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(imageReference.get()).build();\n\n    Function<BuildContext, StepsRunner> stepsRunnerFactory =\n        buildContext -> StepsRunner.begin(buildContext).tarBuildSteps(tarImage.getPath());\n\n    return new Containerizer(\n        DESCRIPTION_FOR_TARBALL, imageConfiguration, stepsRunnerFactory, false);\n  }\n\n  /**\n   * Gets a new {@link Containerizer} that containerizes to a Docker daemon.\n   *\n   * @param dockerClient the {@link DockerClient} to connect\n   * @param dockerDaemonImage the {@link DockerDaemonImage} that defines target Docker daemon\n   * @return a new {@link Containerizer}\n   */\n  public static Containerizer to(DockerClient dockerClient, DockerDaemonImage dockerDaemonImage) {\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(dockerDaemonImage.getImageReference()).build();\n\n    Function<BuildContext, StepsRunner> stepsRunnerFactory =\n        buildContext -> StepsRunner.begin(buildContext).dockerLoadSteps(dockerClient);\n\n    return new Containerizer(\n        DESCRIPTION_FOR_DOCKER_DAEMON, imageConfiguration, stepsRunnerFactory, false);\n  }\n\n  private final String description;\n  private final ImageConfiguration imageConfiguration;\n  private final Function<BuildContext, StepsRunner> stepsRunnerFactory;\n  private final boolean mustBeOnline;\n  private final Set<String> additionalTags = new HashSet<>();\n  private final EventHandlers.Builder eventHandlersBuilder = EventHandlers.builder();\n\n  @Nullable private ExecutorService executorService;\n  private Path baseImageLayersCacheDirectory = DEFAULT_BASE_CACHE_DIRECTORY;\n  @Nullable private Path applicationLayersCacheDirectory;\n  private boolean allowInsecureRegistries = false;\n  private boolean offline = false;\n  private String toolName = DEFAULT_TOOL_NAME;\n  @Nullable private String toolVersion = DEFAULT_TOOL_VERSION;\n  private boolean alwaysCacheBaseImage = false;\n  private ListMultimap<String, String> registryMirrors = ArrayListMultimap.create();\n\n  /** Instantiate with {@link #to}. */\n  private Containerizer(\n      String description,\n      ImageConfiguration imageConfiguration,\n      Function<BuildContext, StepsRunner> stepsRunnerFactory,\n      boolean mustBeOnline) {\n    this.description = description;\n    this.imageConfiguration = imageConfiguration;\n    this.stepsRunnerFactory = stepsRunnerFactory;\n    this.mustBeOnline = mustBeOnline;\n  }\n\n  /**\n   * Adds an additional tag to tag the target image with. For example, the following would\n   * containerize to both {@code gcr.io/my-project/my-image:tag} and {@code\n   * gcr.io/my-project/my-image:tag2}:\n   *\n   * <pre>{@code\n   * Containerizer.to(RegistryImage.named(\"gcr.io/my-project/my-image:tag\")).withAdditionalTag(\"tag2\");\n   * }</pre>\n   *\n   * @param tag the additional tag to push to\n   * @return this\n   */\n  public Containerizer withAdditionalTag(String tag) {\n    Preconditions.checkArgument(ImageReference.isValidTag(tag), \"invalid tag '%s'\", tag);\n    additionalTags.add(tag);\n    return this;\n  }\n\n  /**\n   * Sets the {@link ExecutorService} Jib executes on. Jib, by default, uses {@link\n   * Executors#newCachedThreadPool}.\n   *\n   * @param executorService the {@link ExecutorService}\n   * @return this\n   */\n  public Containerizer setExecutorService(@Nullable ExecutorService executorService) {\n    this.executorService = executorService;\n    return this;\n  }\n\n  /**\n   * Sets the directory to use for caching base image layers. This cache can (and should) be shared\n   * between multiple images. The default base image layers cache directory is {@code [user cache\n   * home]/google-cloud-tools-java/jib} ({@link #DEFAULT_BASE_CACHE_DIRECTORY}. This directory can\n   * be the same directory used for {@link #setApplicationLayersCache}.\n   *\n   * @param cacheDirectory the cache directory\n   * @return this\n   */\n  public Containerizer setBaseImageLayersCache(Path cacheDirectory) {\n    baseImageLayersCacheDirectory = cacheDirectory;\n    return this;\n  }\n\n  /**\n   * Sets the directory to use for caching application layers. This cache can be shared between\n   * multiple images. If not set, a temporary directory will be used as the application layers\n   * cache. This directory can be the same directory used for {@link #setBaseImageLayersCache}.\n   *\n   * @param cacheDirectory the cache directory\n   * @return this\n   */\n  public Containerizer setApplicationLayersCache(Path cacheDirectory) {\n    applicationLayersCacheDirectory = cacheDirectory;\n    return this;\n  }\n\n  /**\n   * Adds the {@code eventConsumer} to handle the {@link JibEvent} with class {@code eventType}. The\n   * order in which handlers are added is the order in which they are called when the event is\n   * dispatched.\n   *\n   * <p><b>Note: Implementations of {@code eventConsumer} must be thread-safe.</b>\n   *\n   * @param eventType the event type that {@code eventConsumer} should handle\n   * @param eventConsumer the event handler\n   * @param <E> the type of {@code eventType}\n   * @return this\n   */\n  public <E extends JibEvent> Containerizer addEventHandler(\n      Class<E> eventType, Consumer<? super E> eventConsumer) {\n    eventHandlersBuilder.add(eventType, eventConsumer);\n    return this;\n  }\n\n  /**\n   * Adds the {@code eventConsumer} to handle all {@link JibEvent} types. See {@link\n   * #addEventHandler(Class, Consumer)} for more details.\n   *\n   * @param eventConsumer the event handler\n   * @return this\n   */\n  public Containerizer addEventHandler(Consumer<JibEvent> eventConsumer) {\n    eventHandlersBuilder.add(JibEvent.class, eventConsumer);\n    return this;\n  }\n\n  /**\n   * Sets whether or not to allow communication over HTTP/insecure HTTPS.\n   *\n   * @param allowInsecureRegistries if {@code true}, insecure connections will be allowed\n   * @return this\n   */\n  public Containerizer setAllowInsecureRegistries(boolean allowInsecureRegistries) {\n    this.allowInsecureRegistries = allowInsecureRegistries;\n    return this;\n  }\n\n  /**\n   * Sets whether or not to run the build in offline mode. In offline mode, the base image is\n   * retrieved from the cache instead of pulled from a registry, and the build will fail if the base\n   * image is not in the cache or if the target is an image registry.\n   *\n   * @param offline if {@code true}, the build will run in offline mode\n   * @return this\n   */\n  public Containerizer setOfflineMode(boolean offline) {\n    if (mustBeOnline && offline) {\n      throw new IllegalStateException(\"Cannot build to a container registry in offline mode\");\n    }\n    this.offline = offline;\n    return this;\n  }\n\n  /**\n   * Sets the name of the tool that is using Jib Core. The tool name is sent as part of the {@code\n   * User-Agent} in registry requests and set as the {@code created_by} in the container layer\n   * history. Defaults to {@code jib-core}.\n   *\n   * @param toolName the name of the tool using this library\n   * @return this\n   */\n  public Containerizer setToolName(String toolName) {\n    this.toolName = toolName;\n    return this;\n  }\n\n  /**\n   * Sets the version of the tool that is using Jib Core. The tool version is sent as part of the\n   * {@code User-Agent} in registry requests and set as the {@code created_by} in the container\n   * layer history. Defaults to the current version of jib-core.\n   *\n   * @param toolVersion the name of the tool using this library\n   * @return this\n   */\n  public Containerizer setToolVersion(@Nullable String toolVersion) {\n    this.toolVersion = toolVersion;\n    return this;\n  }\n\n  /**\n   * Controls the optimization which skips downloading base image layers that exist in a target\n   * registry. If the user does not set this property, then read as false.\n   *\n   * @param alwaysCacheBaseImage if {@code true}, base image layers are always pulled and cached. If\n   *     {@code false}, base image layers will not be pulled/cached if they already exist on the\n   *     target registry.\n   * @return this\n   */\n  public Containerizer setAlwaysCacheBaseImage(boolean alwaysCacheBaseImage) {\n    this.alwaysCacheBaseImage = alwaysCacheBaseImage;\n    return this;\n  }\n\n  /**\n   * Adds mirrors for a base image registry. Jib will try its mirrors in the given order before\n   * finally trying the registry.\n   *\n   * @param registry base image registry for which mirrors are configured\n   * @param mirrors a list of mirrors, where each element is in the form of {@code host[:port]}\n   * @return this\n   */\n  public Containerizer addRegistryMirrors(String registry, List<String> mirrors) {\n    registryMirrors.putAll(registry, mirrors);\n    return this;\n  }\n\n  Set<String> getAdditionalTags() {\n    return ImmutableSet.copyOf(additionalTags);\n  }\n\n  ListMultimap<String, String> getRegistryMirrors() {\n    return ImmutableListMultimap.copyOf(registryMirrors);\n  }\n\n  Optional<ExecutorService> getExecutorService() {\n    return Optional.ofNullable(executorService);\n  }\n\n  Path getBaseImageLayersCacheDirectory() {\n    return baseImageLayersCacheDirectory;\n  }\n\n  Path getApplicationLayersCacheDirectory() throws CacheDirectoryCreationException {\n    if (applicationLayersCacheDirectory == null) {\n      // Create a directory in temp if application layers cache directory is not set.\n      try {\n        Path tmp = Paths.get(System.getProperty(\"java.io.tmpdir\"));\n        applicationLayersCacheDirectory = tmp.resolve(DEFAULT_APPLICATION_CACHE_DIRECTORY_NAME);\n        Files.createDirectories(applicationLayersCacheDirectory);\n      } catch (IOException ex) {\n        throw new CacheDirectoryCreationException(ex);\n      }\n    }\n    return applicationLayersCacheDirectory;\n  }\n\n  EventHandlers buildEventHandlers() {\n    return eventHandlersBuilder.build();\n  }\n\n  boolean getAllowInsecureRegistries() {\n    return allowInsecureRegistries;\n  }\n\n  boolean isOfflineMode() {\n    return offline;\n  }\n\n  String getToolName() {\n    return toolName;\n  }\n\n  @Nullable\n  String getToolVersion() {\n    return toolVersion;\n  }\n\n  boolean getAlwaysCacheBaseImage() {\n    return alwaysCacheBaseImage;\n  }\n\n  String getDescription() {\n    return description;\n  }\n\n  ImageConfiguration getImageConfiguration() {\n    return imageConfiguration;\n  }\n\n  BuildResult run(BuildContext buildContext) throws ExecutionException, InterruptedException {\n    return stepsRunnerFactory.apply(buildContext).run();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/Credential.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.util.Objects;\n\n/** Holds credentials (username and password). */\npublic class Credential {\n  // If the username is set to <token>, the secret would be a refresh token.\n  // https://github.com/docker/cli/blob/master/docs/reference/commandline/login.md#credential-helper-protocol\n  public static final String OAUTH2_TOKEN_USER_NAME = \"<token>\";\n\n  /**\n   * Gets a {@link Credential} configured with a username and password.\n   *\n   * @param username the username\n   * @param password the password\n   * @return a new {@link Credential}\n   */\n  public static Credential from(String username, String password) {\n    return new Credential(username, password);\n  }\n\n  private final String username;\n  private final String password;\n\n  private Credential(String username, String password) {\n    this.username = username;\n    this.password = password;\n  }\n\n  /**\n   * Gets the username.\n   *\n   * @return the username\n   */\n  public String getUsername() {\n    return username;\n  }\n\n  /**\n   * Gets the password.\n   *\n   * @return the password\n   */\n  public String getPassword() {\n    return password;\n  }\n\n  /**\n   * Check whether this credential is an OAuth 2.0 refresh token.\n   *\n   * @return true if this credential is an OAuth 2.0 refresh token.\n   */\n  public boolean isOAuth2RefreshToken() {\n    return OAUTH2_TOKEN_USER_NAME.equals(username);\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof Credential)) {\n      return false;\n    }\n    Credential otherCredential = (Credential) other;\n    return username.equals(otherCredential.username) && password.equals(otherCredential.password);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(username, password);\n  }\n\n  @Override\n  public String toString() {\n    return username + \":\" + password;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/CredentialRetriever.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport java.util.Optional;\n\n/** Retrieves credentials for a registry. */\n@FunctionalInterface\npublic interface CredentialRetriever {\n\n  /**\n   * Fetches the credentials. <b>Implementations must be thread-safe.</b>\n   *\n   * <p>Implementations should return {@link Optional#empty} if no credentials could be fetched with\n   * this {@link CredentialRetriever} (and so other credential retrieval methods may be tried), or\n   * throw an exception something went wrong when fetching the credentials.\n   *\n   * @return the fetched credentials or {@link Optional#empty} if no credentials could be fetched\n   *     with this provider\n   * @throws CredentialRetrievalException if the credential retrieval encountered an exception\n   */\n  Optional<Credential> retrieve() throws CredentialRetrievalException;\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/DescriptorDigest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\nimport com.google.cloud.tools.jib.image.json.DescriptorDigestDeserializer;\nimport com.google.cloud.tools.jib.image.json.DescriptorDigestSerializer;\nimport java.security.DigestException;\n\n/**\n * Represents a SHA-256 content descriptor digest as defined by the Registry HTTP API v2 reference.\n *\n * @see <a\n *     href=\"https://docs.docker.com/registry/spec/api/#content-digests\">https://docs.docker.com/registry/spec/api/#content-digests</a>\n * @see <a href=\"https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests\">OCI\n *     Content Descriptor Digest</a>\n */\n@JsonSerialize(using = DescriptorDigestSerializer.class)\n@JsonDeserialize(using = DescriptorDigestDeserializer.class)\npublic class DescriptorDigest {\n\n  public static final int HASH_LENGTH = 64;\n\n  /** Pattern matches a SHA-256 hash - 32 bytes in lowercase hexadecimal. */\n  private static final String HASH_REGEX = String.format(\"[a-f0-9]{%d}\", HASH_LENGTH);\n\n  /** The algorithm prefix for the digest string. */\n  private static final String DIGEST_PREFIX = \"sha256:\";\n\n  /** Pattern matches a SHA-256 digest - a SHA-256 hash prefixed with \"sha256:\". */\n  static final String DIGEST_REGEX = DIGEST_PREFIX + HASH_REGEX;\n\n  private final String hash;\n\n  /**\n   * Creates a new instance from a valid hash string.\n   *\n   * @param hash the hash to generate the {@link DescriptorDigest} from\n   * @return a new {@link DescriptorDigest} created from the hash\n   * @throws DigestException if the hash is invalid\n   */\n  public static DescriptorDigest fromHash(String hash) throws DigestException {\n    if (!hash.matches(HASH_REGEX)) {\n      throw new DigestException(\"Invalid hash: \" + hash);\n    }\n\n    return new DescriptorDigest(hash);\n  }\n\n  /**\n   * Creates a new instance from a valid digest string.\n   *\n   * @param digest the digest to generate the {@link DescriptorDigest} from\n   * @return a new {@link DescriptorDigest} created from the digest\n   * @throws DigestException if the digest is invalid\n   */\n  public static DescriptorDigest fromDigest(String digest) throws DigestException {\n    if (!digest.matches(DIGEST_REGEX)) {\n      throw new DigestException(\"Invalid digest: \" + digest);\n    }\n\n    // Extracts the hash portion of the digest.\n    String hash = digest.substring(DIGEST_PREFIX.length());\n    return new DescriptorDigest(hash);\n  }\n\n  private DescriptorDigest(String hash) {\n    this.hash = hash;\n  }\n\n  public String getHash() {\n    return hash;\n  }\n\n  @Override\n  public String toString() {\n    return DIGEST_PREFIX + hash;\n  }\n\n  /** Pass-through hash code of the digest string. */\n  @Override\n  public int hashCode() {\n    return hash.hashCode();\n  }\n\n  /** Two digest objects are equal if their digest strings are equal. */\n  @Override\n  public boolean equals(Object obj) {\n    if (obj instanceof DescriptorDigest) {\n      return hash.equals(((DescriptorDigest) obj).hash);\n    }\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic interface DockerClient {\n\n  /**\n   * Validate if the DockerClient is supported.\n   *\n   * @param parameters to be used by the docker client\n   * @return true if conditions are met\n   */\n  boolean supported(Map<String, String> parameters);\n\n  /**\n   * Loads an image tarball into the Docker daemon.\n   *\n   * @see <a\n   *     href=\"https://docs.docker.com/engine/reference/commandline/load/\">https://docs.docker.com/engine/reference/commandline/load</a>\n   * @param imageTarball the built container tarball\n   * @param writtenByteCountListener callback to call when bytes are loaded\n   * @return stdout from {@code docker}\n   * @throws InterruptedException if the 'docker load' process is interrupted\n   * @throws IOException if streaming the blob to 'docker load' fails\n   */\n  String load(ImageTarball imageTarball, Consumer<Long> writtenByteCountListener)\n      throws InterruptedException, IOException;\n\n  /**\n   * Saves an image tarball from the Docker daemon.\n   *\n   * @see <a\n   *     href=\"https://docs.docker.com/engine/reference/commandline/save/\">https://docs.docker.com/engine/reference/commandline/save</a>\n   * @param imageReference the image to save\n   * @param outputPath the destination path to save the output tarball\n   * @param writtenByteCountListener callback to call when bytes are saved\n   * @throws InterruptedException if the 'docker save' process is interrupted\n   * @throws IOException if creating the tarball fails\n   */\n  void save(ImageReference imageReference, Path outputPath, Consumer<Long> writtenByteCountListener)\n      throws InterruptedException, IOException;\n\n  /**\n   * Gets the size, image ID, and diff IDs of an image in the Docker daemon.\n   *\n   * @param imageReference the image to inspect\n   * @return the size, image ID, and diff IDs of the image\n   * @throws IOException if an I/O exception occurs or {@code docker inspect} failed\n   * @throws InterruptedException if the {@code docker inspect} process was interrupted\n   */\n  ImageDetails inspect(ImageReference imageReference) throws IOException, InterruptedException;\n\n  /**\n   * Gets docker info details of local docker installation.\n   *\n   * @return docker info details.\n   * @throws IOException if an I/O exception occurs or {@code docker info} failed\n   * @throws InterruptedException if the {@code docker info} process was interrupted\n   */\n  default DockerInfoDetails info() throws IOException, InterruptedException {\n    return new DockerInfoDetails();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerDaemonImage.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Map;\n\n/** Builds to the Docker daemon. */\npublic class DockerDaemonImage {\n\n  /**\n   * Instantiate with the image reference to tag the built image with. This is the name that shows\n   * up on the Docker daemon.\n   *\n   * @param imageReference the image reference\n   * @return a new {@link DockerDaemonImage}\n   */\n  public static DockerDaemonImage named(ImageReference imageReference) {\n    return new DockerDaemonImage(imageReference);\n  }\n\n  /**\n   * Instantiate with the image reference to tag the built image with. This is the name that shows\n   * up on the Docker daemon.\n   *\n   * @param imageReference the image reference\n   * @return a new {@link DockerDaemonImage}\n   * @throws InvalidImageReferenceException if {@code imageReference} is not a valid image reference\n   */\n  public static DockerDaemonImage named(String imageReference)\n      throws InvalidImageReferenceException {\n    return named(ImageReference.parse(imageReference));\n  }\n\n  private final ImageReference imageReference;\n  private Path dockerExecutable = CliDockerClient.DEFAULT_DOCKER_CLIENT;\n  private Map<String, String> dockerEnvironment = Collections.emptyMap();\n\n  /** Instantiate with {@link #named}. */\n  private DockerDaemonImage(ImageReference imageReference) {\n    this.imageReference = imageReference;\n  }\n\n  /**\n   * Sets the path to the {@code docker} CLI. This is {@code docker} by default.\n   *\n   * @param dockerExecutable the path to the {@code docker} CLI\n   * @return this\n   */\n  public DockerDaemonImage setDockerExecutable(Path dockerExecutable) {\n    this.dockerExecutable = dockerExecutable;\n    return this;\n  }\n\n  /**\n   * Sets the additional environment variables to use when running {@link #dockerExecutable docker}.\n   *\n   * @param dockerEnvironment additional environment variables\n   * @return this\n   */\n  public DockerDaemonImage setDockerEnvironment(Map<String, String> dockerEnvironment) {\n    this.dockerEnvironment = dockerEnvironment;\n    return this;\n  }\n\n  ImageReference getImageReference() {\n    return imageReference;\n  }\n\n  Path getDockerExecutable() {\n    return dockerExecutable;\n  }\n\n  Map<String, String> getDockerEnvironment() {\n    return dockerEnvironment;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java",
    "content": "/*\n * Copyright 2024 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\n\n/** Contains docker info details outputted by {@code docker info}. */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class DockerInfoDetails implements JsonTemplate {\n\n  @JsonProperty(\"OSType\")\n  private String osType = \"\";\n\n  @JsonProperty(\"Architecture\")\n  private String architecture = \"\";\n\n  public String getOsType() {\n    return osType;\n  }\n\n  public String getArchitecture() {\n    return architecture;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageDetails.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.security.DigestException;\nimport java.util.List;\n\npublic interface ImageDetails {\n\n  long getSize();\n\n  DescriptorDigest getImageId() throws DigestException;\n\n  List<DescriptorDigest> getDiffIds() throws DigestException;\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageReference.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.registry.RegistryAliasGroup;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Strings;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\n\n/**\n * Represents an image reference.\n *\n * @see <a\n *     href=\"https://github.com/docker/distribution/blob/master/reference/reference.go\">https://github.com/docker/distribution/blob/master/reference/reference.go</a>\n * @see <a\n *     href=\"https://docs.docker.com/engine/reference/commandline/tag/#extended-description\">https://docs.docker.com/engine/reference/commandline/tag/#extended-description</a>\n */\npublic class ImageReference {\n\n  private static final String DOCKER_HUB_REGISTRY = \"registry-1.docker.io\";\n  private static final String DEFAULT_TAG = \"latest\";\n  private static final String LIBRARY_REPOSITORY_PREFIX = \"library/\";\n  private static final String SCRATCH = \"scratch\";\n\n  /**\n   * Matches all sequences of alphanumeric characters possibly separated by any number of dashes in\n   * the middle.\n   */\n  private static final String REGISTRY_COMPONENT_REGEX =\n      \"(?:[a-zA-Z\\\\d]|(?:[a-zA-Z\\\\d][a-zA-Z\\\\d-]*[a-zA-Z\\\\d]))\";\n\n  /**\n   * Matches sequences of {@code REGISTRY_COMPONENT_REGEX} separated by a dot, with an optional\n   * {@code :port} at the end.\n   */\n  private static final String REGISTRY_REGEX =\n      String.format(\"%s(?:\\\\.%s)*(?::\\\\d+)?\", REGISTRY_COMPONENT_REGEX, REGISTRY_COMPONENT_REGEX);\n\n  /**\n   * Matches all sequences of alphanumeric characters separated by a separator.\n   *\n   * <p>A separator is either an underscore, a dot, two underscores, or any number of dashes.\n   */\n  private static final String REPOSITORY_COMPONENT_REGEX = \"[a-z\\\\d]+(?:(?:[_.]|__|-+)[a-z\\\\d]+)*\";\n\n  /** Matches all repetitions of {@code REPOSITORY_COMPONENT_REGEX} separated by a backslash. */\n  private static final String REPOSITORY_REGEX =\n      String.format(\"(?:%s/)*%s\", REPOSITORY_COMPONENT_REGEX, REPOSITORY_COMPONENT_REGEX);\n\n  /** Matches a tag of max length 128. */\n  private static final String TAG_REGEX = \"[\\\\w][\\\\w.-]{0,127}\";\n\n  /**\n   * Matches a full image reference, which is the registry, repository, and tag/digest separated by\n   * backslashes. The repository is required, but the registry and tag/digest are optional.\n   */\n  private static final String REFERENCE_REGEX =\n      String.format(\n          \"^(?:(%s)/)?(%s)(?::(%s))?(?:@(%s))?$\",\n          REGISTRY_REGEX, REPOSITORY_REGEX, TAG_REGEX, DescriptorDigest.DIGEST_REGEX);\n\n  private static final Pattern REFERENCE_PATTERN = Pattern.compile(REFERENCE_REGEX);\n\n  /**\n   * Parses a string {@code reference} into an {@link ImageReference}.\n   *\n   * <p>Image references should generally be in the form: {@code <registry>/<repository>:<tag>} For\n   * example, an image reference could be {@code gcr.io/k8s-skaffold/skaffold:v1.20.0}.\n   *\n   * <p>See <a\n   * href=\"https://docs.docker.com/engine/reference/commandline/tag/#extended-description\">https://docs.docker.com/engine/reference/commandline/tag/#extended-description</a>\n   * for a description of valid image reference format. Note, however, that the image reference is\n   * referred confusingly as {@code tag} on that page.\n   *\n   * @param reference the string to parse\n   * @return an {@link ImageReference} parsed from the string\n   * @throws InvalidImageReferenceException if {@code reference} is formatted incorrectly\n   */\n  public static ImageReference parse(String reference) throws InvalidImageReferenceException {\n    if (reference.equals(SCRATCH)) {\n      return ImageReference.scratch();\n    }\n\n    Matcher matcher = REFERENCE_PATTERN.matcher(reference);\n\n    if (!matcher.find() || matcher.groupCount() < 4) {\n      throw new InvalidImageReferenceException(reference);\n    }\n\n    String registry = matcher.group(1);\n    String repository = matcher.group(2);\n    String tag = matcher.group(3);\n    String digest = matcher.group(4);\n\n    // If no registry was matched, use Docker Hub by default.\n    if (Strings.isNullOrEmpty(registry)) {\n      registry = DOCKER_HUB_REGISTRY;\n    }\n\n    if (Strings.isNullOrEmpty(repository)) {\n      throw new InvalidImageReferenceException(reference);\n    }\n    /*\n     * If a registry was matched but it does not contain any dots or colons, it should actually be\n     * part of the repository unless it is \"localhost\".\n     *\n     * See https://github.com/docker/distribution/blob/245ca4659e09e9745f3cc1217bf56e946509220c/reference/normalize.go#L62\n     */\n    if (!registry.contains(\".\") && !registry.contains(\":\") && !\"localhost\".equals(registry)) {\n      repository = registry + \"/\" + repository;\n      registry = DOCKER_HUB_REGISTRY;\n    }\n\n    /*\n     * For Docker Hub, if the repository is only one component, then it should be prefixed with\n     * 'library/'.\n     *\n     * See https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-from-docker-hub\n     */\n    if (DOCKER_HUB_REGISTRY.equals(registry) && repository.indexOf('/') < 0) {\n      repository = LIBRARY_REPOSITORY_PREFIX + repository;\n    }\n\n    if (Strings.isNullOrEmpty(tag) && Strings.isNullOrEmpty(digest)) {\n      tag = DEFAULT_TAG;\n    }\n    if (Strings.isNullOrEmpty(tag)) {\n      tag = null;\n    }\n    if (Strings.isNullOrEmpty(digest)) {\n      digest = null;\n    }\n\n    return new ImageReference(registry, repository, tag, digest);\n  }\n\n  /**\n   * Constructs an {@link ImageReference} from the image reference components, consisting of an\n   * optional registry, a repository, and an optional tag.\n   *\n   * @param registry the image registry, or {@code null} to use the default registry (Docker Hub)\n   * @param repository the image repository\n   * @param qualifier the image tag or digest, or {@code null} to use the default tag ({@code\n   *     latest}).\n   * @return an {@link ImageReference} built from the given registry, repository, and tag\n   */\n  public static ImageReference of(\n      @Nullable String registry, String repository, @Nullable String qualifier) {\n    if (!Strings.isNullOrEmpty(qualifier) && isValidDigest(qualifier)) {\n      return of(registry, repository, null, qualifier);\n    }\n    return of(registry, repository, qualifier, null);\n  }\n\n  /**\n   * Constructs an {@link ImageReference} from the image reference components, consisting of an\n   * optional registry, a repository, an optional tag, and an optional digest. If neither the tag\n   * nor digest are specified, {@code tag} will take on the default value of {@code latest}.\n   *\n   * @param registry the image registry, or {@code null} to use the default registry (Docker Hub)\n   * @param repository the image repository\n   * @param tag the image tag, or {@code null} to use the default tag ({@code latest})\n   * @param digest the image digest\n   * @return an {@link ImageReference} built from the given registry, repository, and tag\n   */\n  public static ImageReference of(\n      @Nullable String registry, String repository, @Nullable String tag, @Nullable String digest) {\n    Preconditions.checkArgument(Strings.isNullOrEmpty(registry) || isValidRegistry(registry));\n    Preconditions.checkArgument(isValidRepository(repository));\n    Preconditions.checkArgument(Strings.isNullOrEmpty(tag) || isValidTag(tag));\n    Preconditions.checkArgument(Strings.isNullOrEmpty(digest) || isValidDigest(digest));\n\n    if (Strings.isNullOrEmpty(registry)) {\n      registry = DOCKER_HUB_REGISTRY;\n    }\n    if (Strings.isNullOrEmpty(tag) && Strings.isNullOrEmpty(digest)) {\n      tag = DEFAULT_TAG;\n    }\n    return new ImageReference(registry, repository, tag, digest);\n  }\n\n  /**\n   * Constructs an {@link ImageReference} with an empty registry and tag component, and repository\n   * set to \"scratch\".\n   *\n   * @return an {@link ImageReference} with an empty registry and tag component, and repository set\n   *     to \"scratch\"\n   */\n  public static ImageReference scratch() {\n    return new ImageReference(\"\", SCRATCH, null, null);\n  }\n\n  /**\n   * Returns {@code true} if {@code registry} is a valid registry string. For example, a valid\n   * registry could be {@code gcr.io} or {@code localhost:5000}.\n   *\n   * @param registry the registry to check\n   * @return {@code true} if is a valid registry; {@code false} otherwise\n   */\n  public static boolean isValidRegistry(String registry) {\n    return registry.matches(REGISTRY_REGEX);\n  }\n\n  /**\n   * Returns {@code true} if {@code repository} is a valid repository string. For example, a valid\n   * repository string could be {@code my-repository} or {@code k8s-skaffold/skaffold}.\n   *\n   * @param repository the repository to check\n   * @return {@code true} if is a valid repository; {@code false} otherwise\n   */\n  public static boolean isValidRepository(String repository) {\n    return repository.matches(REPOSITORY_REGEX);\n  }\n\n  /**\n   * Returns {@code true} if {@code tag} is a valid tag string. For example, a valid tag could be\n   * {@code v120.5-release}.\n   *\n   * @param tag the tag to check\n   * @return {@code true} if is a valid tag; {@code false} otherwise\n   */\n  public static boolean isValidTag(String tag) {\n    return tag.matches(TAG_REGEX);\n  }\n\n  /**\n   * Returns {@code true} if {@code digest} is a valid digest string. For example, a valid digest\n   * could be {@code sha256:868fd30a0e47b8d8ac485df174795b5e2fe8a6c8f056cc707b232d65b8a1ab68}.\n   *\n   * @param digest the digest to check\n   * @return {@code true} if is a valid digest; {@code false} otherwise\n   */\n  public static boolean isValidDigest(String digest) {\n    return digest.matches(DescriptorDigest.DIGEST_REGEX);\n  }\n\n  /**\n   * Returns {@code true} if {@code tag} is the default tag ({@code latest}); {@code false} if not.\n   *\n   * @param tag the tag to check\n   * @return {@code true} if {@code tag} is the default tag ({@code latest}); {@code false} if not\n   */\n  public static boolean isDefaultTag(@Nullable String tag) {\n    return DEFAULT_TAG.equals(tag);\n  }\n\n  private final String registry;\n  private final String repository;\n  @Nullable private final String tag;\n  @Nullable private final String digest;\n\n  /** Construct with {@link #parse}. */\n  private ImageReference(\n      String registry, String repository, @Nullable String tag, @Nullable String digest) {\n    Preconditions.checkArgument(\n        SCRATCH.equals(repository) || !Strings.isNullOrEmpty(tag) || !Strings.isNullOrEmpty(digest),\n        \"Either tag or digest needs to be set.\");\n    this.registry = RegistryAliasGroup.getHost(registry);\n    this.repository = repository;\n    this.tag = tag;\n    this.digest = digest;\n  }\n\n  /**\n   * Gets the registry portion of the {@link ImageReference}.\n   *\n   * @return the registry host\n   */\n  public String getRegistry() {\n    return registry;\n  }\n\n  /**\n   * Gets the repository portion of the {@link ImageReference}.\n   *\n   * @return the repository\n   */\n  public String getRepository() {\n    return repository;\n  }\n\n  /**\n   * Gets the tag portion of the {@link ImageReference}.\n   *\n   * @return the optional tag\n   */\n  public Optional<String> getTag() {\n    return Optional.ofNullable(tag);\n  }\n\n  /**\n   * Gets the digest portion of the {@link ImageReference}.\n   *\n   * @return the optional digest\n   */\n  public Optional<String> getDigest() {\n    return Optional.ofNullable(digest);\n  }\n\n  /**\n   * Gets the digest portion of the {@link ImageReference} if set, else returns the tag.\n   *\n   * @return the digest if set, else the tag\n   */\n  public String getQualifier() {\n    if (!Strings.isNullOrEmpty(digest)) {\n      return digest;\n    }\n    return Preconditions.checkNotNull(tag);\n  }\n\n  /**\n   * Returns {@code true} if the {@link ImageReference} uses the default tag ({@code latest});\n   * {@code false} if not.\n   *\n   * @return {@code true} if uses the default tag; {@code false} if not\n   */\n  public boolean usesDefaultTag() {\n    return isDefaultTag(tag);\n  }\n\n  /**\n   * Returns {@code true} if the {@link ImageReference} is a scratch image; {@code false} if not.\n   *\n   * @return {@code true} if the {@link ImageReference} is a scratch image; {@code false} if not\n   */\n  public boolean isScratch() {\n    return \"\".equals(registry)\n        && SCRATCH.equals(repository)\n        && Strings.isNullOrEmpty(tag)\n        && Strings.isNullOrEmpty(digest);\n  }\n\n  /**\n   * Gets an {@link ImageReference} with the same registry and repository, but a different tag or\n   * digest.\n   *\n   * @param newQualifier the new tag or digest\n   * @return an {@link ImageReference} with the same registry/repository and the new tag or digest\n   */\n  public ImageReference withQualifier(String newQualifier) {\n    if (isValidDigest(newQualifier)) {\n      return ImageReference.of(registry, repository, tag, newQualifier);\n    }\n    return ImageReference.of(registry, repository, newQualifier, digest);\n  }\n\n  /**\n   * Stringifies the {@link ImageReference}.\n   *\n   * @return the image reference in Docker-readable format (inverse of {@link #parse})\n   */\n  @Override\n  public String toString() {\n    return toString(false);\n  }\n\n  /**\n   * Stringifies the {@link ImageReference}. If the digest is set, the result will include the\n   * digest and no tag. Otherwise, the result will include the tag, or {@code latest} if no tag is\n   * set.\n   *\n   * @return the image reference in Docker-readable format including a qualifier.\n   */\n  public String toStringWithQualifier() {\n    return toString(true);\n  }\n\n  /**\n   * Stringifies the {@link ImageReference}.\n   *\n   * @param singleQualifier when {@code true}, the result will include exactly one qualifier (i.e.\n   *     the digest, or the tag if the digest is missing). When {@code false}, the result will\n   *     include all specified qualifiers (omitting tag if the default {@code latest} is used).\n   * @return the image reference in a Docker-readable format.\n   */\n  private String toString(boolean singleQualifier) {\n    if (isScratch()) {\n      return SCRATCH;\n    }\n\n    StringBuilder referenceString = new StringBuilder();\n\n    if (!DOCKER_HUB_REGISTRY.equals(registry)) {\n      // Use registry and repository if not Docker Hub.\n      referenceString.append(registry).append('/').append(repository);\n\n    } else if (repository.startsWith(LIBRARY_REPOSITORY_PREFIX)) {\n      // If Docker Hub and repository has 'library/' prefix, remove the 'library/' prefix.\n      referenceString.append(repository.substring(LIBRARY_REPOSITORY_PREFIX.length()));\n\n    } else {\n      // Use just repository if Docker Hub.\n      referenceString.append(repository);\n    }\n\n    if (singleQualifier) {\n      if (!Strings.isNullOrEmpty(digest)) {\n        referenceString.append('@').append(digest);\n      } else {\n        referenceString.append(':').append(tag);\n      }\n    } else {\n      if (!Strings.isNullOrEmpty(tag) && !usesDefaultTag()) {\n        referenceString.append(':').append(tag);\n      }\n      if (!Strings.isNullOrEmpty(digest)) {\n        referenceString.append('@').append(digest);\n      }\n    }\n\n    return referenceString.toString();\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof ImageReference)) {\n      return false;\n    }\n    ImageReference otherImageReference = (ImageReference) other;\n    return registry.equals(otherImageReference.registry)\n        && repository.equals(otherImageReference.repository)\n        && Objects.equals(tag, otherImageReference.tag)\n        && Objects.equals(digest, otherImageReference.digest);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(registry, repository, tag, digest);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/InsecureRegistryException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.net.URL;\n\n/**\n * Throw when attempting to access an insecure registry when only secure connections are allowed.\n */\npublic class InsecureRegistryException extends RegistryException {\n\n  /**\n   * Creates a new exception with a human readable message.\n   *\n   * @param insecureUrl the insecure url that is attempted to be accessed\n   * @param cause the underlying cause that triggered this exception\n   */\n  public InsecureRegistryException(URL insecureUrl, Throwable cause) {\n    super(\n        \"Failed to verify the server at \"\n            + insecureUrl\n            + \" because only secure connections are allowed.\",\n        cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/InvalidImageReferenceException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\n/** Thrown when attempting to parse an invalid image reference. */\npublic class InvalidImageReferenceException extends Exception {\n\n  private final String reference;\n\n  public InvalidImageReferenceException(String reference) {\n    super(\"Invalid image reference: \" + reference);\n    this.reference = reference;\n  }\n\n  public String getInvalidReference() {\n    return reference;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/JavaContainerBuilder.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ModificationTimeProvider;\nimport com.google.cloud.tools.jib.api.buildplan.RelativeUnixPath;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Streams;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.NoSuchFileException;\nimport java.nio.file.NotDirectoryException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.EnumMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\n/** Creates a {@link JibContainerBuilder} for containerizing Java applications. */\npublic class JavaContainerBuilder {\n\n  /** Holds a directory and a filter. */\n  private static class PathPredicatePair {\n\n    private final Path path;\n    private final Predicate<Path> predicate;\n\n    private PathPredicatePair(Path path, Predicate<Path> predicate) {\n      this.path = path;\n      this.predicate = predicate;\n    }\n  }\n\n  /** Represents the different types of layers for a Java application. */\n  public enum LayerType {\n    DEPENDENCIES(\"dependencies\"),\n    SNAPSHOT_DEPENDENCIES(\"snapshot dependencies\"),\n    PROJECT_DEPENDENCIES(\"project dependencies\"),\n    RESOURCES(\"resources\"),\n    CLASSES(\"classes\"),\n    EXTRA_FILES(\"extra files\"),\n    JVM_ARG_FILES(\"jvm arg files\");\n\n    private final String name;\n\n    /**\n     * Initializes with a name for the layer.\n     *\n     * @param name name to set for the layer; does not affect the contents of the layer\n     */\n    LayerType(String name) {\n      this.name = name;\n    }\n\n    public String getName() {\n      return name;\n    }\n  }\n\n  /**\n   * Creates a new {@link JavaContainerBuilder} that uses distroless java as the base image. For\n   * more information on {@code gcr.io/distroless/java}, see <a\n   * href=\"https://github.com/GoogleContainerTools/distroless\">the distroless repository</a>.\n   *\n   * @return a new {@link JavaContainerBuilder}\n   * @see <a href=\"https://github.com/GoogleContainerTools/distroless\">The distroless repository</a>\n   * @deprecated Use {@code from()} with the image reference {@code gcr.io/distroless/java}.\n   */\n  @Deprecated\n  public static JavaContainerBuilder fromDistroless() {\n    try {\n      return from(RegistryImage.named(\"gcr.io/distroless/java\"));\n    } catch (InvalidImageReferenceException ignored) {\n      throw new IllegalStateException(\"Unreachable\");\n    }\n  }\n\n  /**\n   * The default app root in the image. For example, if this is set to {@code \"/app\"}, dependency\n   * JARs will be in {@code \"/app/libs\"}.\n   */\n  public static final String DEFAULT_APP_ROOT = \"/app\";\n\n  /**\n   * The default webapp root in the image. For example, if this is set to {@code\n   * \"/jetty/webapps/ROOT\"}, dependency JARs will be in {@code \"/jetty/webapps/ROOT/WEB-INF/lib\"}.\n   *\n   * @deprecated Use the string {@code \"/jetty/webapps/ROOT\"}.\n   */\n  @Deprecated public static final String DEFAULT_WEB_APP_ROOT = \"/jetty/webapps/ROOT\";\n\n  /**\n   * Creates a new {@link JavaContainerBuilder} that uses distroless jetty as the base image. For\n   * more information on {@code gcr.io/distroless/java}, see <a\n   * href=\"https://github.com/GoogleContainerTools/distroless\">the distroless repository</a>.\n   *\n   * @return a new {@link JavaContainerBuilder}\n   * @see <a href=\"https://github.com/GoogleContainerTools/distroless\">The distroless repository</a>\n   * @deprecated Use {@code from()} with the image reference {@code gcr.io/distroless/java/jetty}\n   *     and change the app root by calling {@code\n   *     JavaContainerBuilder.setAppRoot(\"/jetty/webapps/ROOT\")}.\n   */\n  @Deprecated\n  public static JavaContainerBuilder fromDistrolessJetty() {\n    try {\n      return from(RegistryImage.named(\"gcr.io/distroless/java/jetty\"))\n          .setAppRoot(AbsoluteUnixPath.get(DEFAULT_WEB_APP_ROOT));\n    } catch (InvalidImageReferenceException ignored) {\n      throw new IllegalStateException(\"Unreachable\");\n    }\n  }\n\n  /**\n   * Creates a new {@link JavaContainerBuilder} with the specified base image reference. The type of\n   * base image can be specified using a prefix; see {@link Jib#from(String)} for the accepted\n   * prefixes.\n   *\n   * @param baseImageReference the base image reference\n   * @return a new {@link JavaContainerBuilder}\n   * @throws InvalidImageReferenceException if {@code baseImageReference} is invalid\n   */\n  public static JavaContainerBuilder from(String baseImageReference)\n      throws InvalidImageReferenceException {\n    return new JavaContainerBuilder(Jib.from(baseImageReference));\n  }\n\n  /**\n   * Creates a new {@link JavaContainerBuilder} with the specified base image reference.\n   *\n   * @param baseImageReference the base image reference\n   * @return a new {@link JavaContainerBuilder}\n   */\n  public static JavaContainerBuilder from(ImageReference baseImageReference) {\n    return from(RegistryImage.named(baseImageReference));\n  }\n\n  /**\n   * Creates a new {@link JavaContainerBuilder} with the specified base image.\n   *\n   * @param registryImage the {@link RegistryImage} that defines base container registry and\n   *     credentials\n   * @return a new {@link JavaContainerBuilder}\n   */\n  public static JavaContainerBuilder from(RegistryImage registryImage) {\n    return new JavaContainerBuilder(Jib.from(registryImage));\n  }\n\n  /**\n   * Starts building the container from a base image stored in the Docker cache. Requires a running\n   * Docker daemon.\n   *\n   * @param dockerDaemonImage the {@link DockerDaemonImage} that defines the base image and Docker\n   *     client\n   * @return a new {@link JavaContainerBuilder}\n   */\n  public static JavaContainerBuilder from(DockerDaemonImage dockerDaemonImage) {\n    return new JavaContainerBuilder(Jib.from(dockerDaemonImage));\n  }\n\n  /**\n   * Starts building the container from a tarball.\n   *\n   * @param tarImage the {@link TarImage} that defines the path to the base image\n   * @return a new {@link JavaContainerBuilder}\n   */\n  public static JavaContainerBuilder from(TarImage tarImage) {\n    return new JavaContainerBuilder(Jib.from(tarImage));\n  }\n\n  private final JibContainerBuilder jibContainerBuilder;\n  private final List<String> jvmFlags = new ArrayList<>();\n  private final LinkedHashSet<LayerType> classpathOrder = new LinkedHashSet<>(4);\n\n  // Keeps track of files to add to the image, by system path\n  private final List<PathPredicatePair> addedResources = new ArrayList<>();\n  private final List<PathPredicatePair> addedClasses = new ArrayList<>();\n  private final List<Path> addedDependencies = new ArrayList<>();\n  private final List<Path> addedSnapshotDependencies = new ArrayList<>();\n  private final List<Path> addedProjectDependencies = new ArrayList<>();\n  private final List<Path> addedOthers = new ArrayList<>();\n\n  private AbsoluteUnixPath appRoot = AbsoluteUnixPath.get(DEFAULT_APP_ROOT);\n  private RelativeUnixPath classesDestination = RelativeUnixPath.get(\"classes\");\n  private RelativeUnixPath resourcesDestination = RelativeUnixPath.get(\"resources\");\n  private RelativeUnixPath dependenciesDestination = RelativeUnixPath.get(\"libs\");\n  private RelativeUnixPath othersDestination = RelativeUnixPath.get(\"classpath\");\n  @Nullable private String mainClass;\n  private ModificationTimeProvider modificationTimeProvider =\n      FileEntriesLayer.DEFAULT_MODIFICATION_TIME_PROVIDER;\n\n  private JavaContainerBuilder(JibContainerBuilder jibContainerBuilder) {\n    this.jibContainerBuilder = jibContainerBuilder;\n  }\n\n  /**\n   * Sets the app root of the container image (useful for building WAR containers).\n   *\n   * @param appRoot the absolute path of the app on the container ({@code /app} by default)\n   * @return this\n   */\n  public JavaContainerBuilder setAppRoot(String appRoot) {\n    return setAppRoot(AbsoluteUnixPath.get(appRoot));\n  }\n\n  /**\n   * Sets the app root of the container image (useful for building WAR containers).\n   *\n   * @param appRoot the absolute path of the app on the container ({@code /app} by default)\n   * @return this\n   */\n  public JavaContainerBuilder setAppRoot(AbsoluteUnixPath appRoot) {\n    this.appRoot = appRoot;\n    return this;\n  }\n\n  /**\n   * Sets the destination directory of the classes added to the container (relative to the app\n   * root).\n   *\n   * @param classesDestination the path to the classes directory, relative to the app root\n   * @return this\n   */\n  public JavaContainerBuilder setClassesDestination(RelativeUnixPath classesDestination) {\n    this.classesDestination = classesDestination;\n    return this;\n  }\n\n  /**\n   * Sets the destination directory of the resources added to the container (relative to the app\n   * root).\n   *\n   * @param resourcesDestination the path to the resources directory, relative to the app root\n   * @return this\n   */\n  public JavaContainerBuilder setResourcesDestination(RelativeUnixPath resourcesDestination) {\n    this.resourcesDestination = resourcesDestination;\n    return this;\n  }\n\n  /**\n   * Sets the destination directory of the dependencies added to the container (relative to the app\n   * root).\n   *\n   * @param dependenciesDestination the path to the dependencies directory, relative to the app root\n   * @return this\n   */\n  public JavaContainerBuilder setDependenciesDestination(RelativeUnixPath dependenciesDestination) {\n    this.dependenciesDestination = dependenciesDestination;\n    return this;\n  }\n\n  /**\n   * Sets the destination directory of additional classpath files added to the container (relative\n   * to the app root).\n   *\n   * @param othersDestination the additional classpath directory, relative to the app root\n   * @return this\n   */\n  public JavaContainerBuilder setOthersDestination(RelativeUnixPath othersDestination) {\n    this.othersDestination = othersDestination;\n    return this;\n  }\n\n  /**\n   * Adds dependency JARs to the image. Duplicate JAR filenames across all dependencies are renamed\n   * with the filesize in order to avoid collisions.\n   *\n   * @param dependencyFiles the list of dependency JARs to add to the image\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addDependencies(List<Path> dependencyFiles) throws IOException {\n    // Make sure all files exist before adding any\n    for (Path file : dependencyFiles) {\n      if (!Files.exists(file)) {\n        throw new NoSuchFileException(file.toString());\n      }\n    }\n    addedDependencies.addAll(dependencyFiles);\n    classpathOrder.add(LayerType.DEPENDENCIES);\n    return this;\n  }\n\n  /**\n   * Adds dependency JARs to the image. Duplicate JAR filenames across all dependencies are renamed\n   * with the filesize in order to avoid collisions.\n   *\n   * @param dependencyFiles the list of dependency JARs to add to the image\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addDependencies(Path... dependencyFiles) throws IOException {\n    return addDependencies(Arrays.asList(dependencyFiles));\n  }\n\n  /**\n   * Adds snapshot dependency JARs to the image. Duplicate JAR filenames across all dependencies are\n   * renamed with the filesize in order to avoid collisions.\n   *\n   * @param dependencyFiles the list of dependency JARs to add to the image\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addSnapshotDependencies(List<Path> dependencyFiles)\n      throws IOException {\n    // Make sure all files exist before adding any\n    for (Path file : dependencyFiles) {\n      if (!Files.exists(file)) {\n        throw new NoSuchFileException(file.toString());\n      }\n    }\n    addedSnapshotDependencies.addAll(dependencyFiles);\n    classpathOrder.add(LayerType.DEPENDENCIES); // this is a single classpath entry with all deps\n    return this;\n  }\n\n  /**\n   * Adds snapshot dependency JARs to the image. Duplicate JAR filenames across all dependencies are\n   * renamed with the filesize in order to avoid collisions.\n   *\n   * @param dependencyFiles the list of dependency JARs to add to the image\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addSnapshotDependencies(Path... dependencyFiles) throws IOException {\n    return addSnapshotDependencies(Arrays.asList(dependencyFiles));\n  }\n\n  /**\n   * Adds project dependency JARs to the image. Generally, project dependency are jars produced from\n   * source in this project as part of other modules/sub-projects. Duplicate JAR filenames across\n   * all dependencies are renamed with the filesize in order to avoid collisions.\n   *\n   * @param dependencyFiles the list of dependency JARs to add to the image\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addProjectDependencies(List<Path> dependencyFiles)\n      throws IOException {\n    // Make sure all files exist before adding any\n    for (Path file : dependencyFiles) {\n      if (!Files.exists(file)) {\n        throw new NoSuchFileException(file.toString());\n      }\n    }\n    addedProjectDependencies.addAll(dependencyFiles);\n    classpathOrder.add(LayerType.DEPENDENCIES); // this is a single classpath entry with all deps\n    return this;\n  }\n\n  /**\n   * Adds project dependency JARs to the image. Generally, project dependency are jars produced from\n   * source in this project as part of other modules/sub-projects. Duplicate JAR filenames across\n   * all dependencies are renamed with the filesize in order to avoid collisions.\n   *\n   * @param dependencyFiles the list of dependency JARs to add to the image\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addProjectDependencies(Path... dependencyFiles) throws IOException {\n    return addProjectDependencies(Arrays.asList(dependencyFiles));\n  }\n\n  /**\n   * Adds the contents of a resources directory to the image.\n   *\n   * @param resourceFilesDirectory the directory containing the project's resources\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addResources(Path resourceFilesDirectory) throws IOException {\n    return addResources(resourceFilesDirectory, path -> true);\n  }\n\n  /**\n   * Adds the contents of a resources directory to the image.\n   *\n   * @param resourceFilesDirectory the directory containing the project's resources\n   * @param pathFilter filter that determines which files (not directories) should be added\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addResources(Path resourceFilesDirectory, Predicate<Path> pathFilter)\n      throws IOException {\n    classpathOrder.add(LayerType.RESOURCES);\n    return addDirectory(addedResources, resourceFilesDirectory, pathFilter);\n  }\n\n  /**\n   * Adds the contents of a classes directory to the image.\n   *\n   * @param classFilesDirectory the directory containing the class files\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addClasses(Path classFilesDirectory) throws IOException {\n    return addClasses(classFilesDirectory, path -> true);\n  }\n\n  /**\n   * Adds the contents of a classes directory to the image.\n   *\n   * @param classFilesDirectory the directory containing the class files\n   * @param pathFilter filter that determines which files (not directories) should be added\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addClasses(Path classFilesDirectory, Predicate<Path> pathFilter)\n      throws IOException {\n    classpathOrder.add(LayerType.CLASSES);\n    return addDirectory(addedClasses, classFilesDirectory, pathFilter);\n  }\n\n  /**\n   * Adds additional files to the classpath. If {@code otherFiles} contains a directory, the files\n   * within are added recursively, maintaining the directory structure. For files in {@code\n   * otherFiles}, files with duplicate filenames will be overwritten (e.g. if {@code otherFiles}\n   * contains '/loser/messages.txt' and '/winner/messages.txt', only the second 'messages.txt' is\n   * added.\n   *\n   * @param otherFiles the list of files to add\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addToClasspath(List<Path> otherFiles) throws IOException {\n    // Make sure all files exist before adding any\n    for (Path file : otherFiles) {\n      if (!Files.exists(file)) {\n        throw new NoSuchFileException(file.toString());\n      }\n    }\n    classpathOrder.add(LayerType.EXTRA_FILES);\n    addedOthers.addAll(otherFiles);\n    return this;\n  }\n\n  /**\n   * Adds additional files to the classpath. If {@code otherFiles} contains a directory, the files\n   * within are added recursively, maintaining the directory structure. For files in {@code\n   * otherFiles}, files with duplicate filenames will be overwritten (e.g. if {@code otherFiles}\n   * contains '/loser/messages.txt' and '/winner/messages.txt', only the second 'messages.txt' is\n   * added.\n   *\n   * @param otherFiles the list of files to add\n   * @return this\n   * @throws IOException if adding the layer fails\n   */\n  public JavaContainerBuilder addToClasspath(Path... otherFiles) throws IOException {\n    return addToClasspath(Arrays.asList(otherFiles));\n  }\n\n  /**\n   * Adds a JVM flag to use when starting the application.\n   *\n   * @param jvmFlag the JVM flag to add\n   * @return this\n   */\n  public JavaContainerBuilder addJvmFlag(String jvmFlag) {\n    jvmFlags.add(jvmFlag);\n    return this;\n  }\n\n  /**\n   * Adds JVM flags to use when starting the application.\n   *\n   * @param jvmFlags the list of JVM flags to add\n   * @return this\n   */\n  public JavaContainerBuilder addJvmFlags(List<String> jvmFlags) {\n    this.jvmFlags.addAll(jvmFlags);\n    return this;\n  }\n\n  /**\n   * Adds JVM flags to use when starting the application.\n   *\n   * @param jvmFlags the list of JVM flags to add\n   * @return this\n   */\n  public JavaContainerBuilder addJvmFlags(String... jvmFlags) {\n    this.jvmFlags.addAll(Arrays.asList(jvmFlags));\n    return this;\n  }\n\n  /**\n   * Sets the container entrypoint with the specified main class. The entrypoint will be left\n   * unconfigured if this method is not called. To find the main class from {@code .class} files,\n   * use {@link MainClassFinder}.\n   *\n   * @param mainClass the main class used to start the application\n   * @return this\n   * @see MainClassFinder\n   */\n  public JavaContainerBuilder setMainClass(String mainClass) {\n    this.mainClass = mainClass;\n    return this;\n  }\n\n  /**\n   * Sets the modification time provider for container files.\n   *\n   * @param modificationTimeProvider a provider that takes a source path and destination path on the\n   *     container and returns the file modification time that should be set for that path\n   * @return this\n   */\n  public JavaContainerBuilder setModificationTimeProvider(\n      ModificationTimeProvider modificationTimeProvider) {\n    this.modificationTimeProvider = modificationTimeProvider;\n    return this;\n  }\n\n  /**\n   * Returns a new {@link JibContainerBuilder} using the parameters specified on the {@link\n   * JavaContainerBuilder}.\n   *\n   * @return a new {@link JibContainerBuilder} using the parameters specified on the {@link\n   *     JavaContainerBuilder}\n   * @throws IOException if building the {@link JibContainerBuilder} fails.\n   */\n  public JibContainerBuilder toContainerBuilder() throws IOException {\n    if (mainClass == null && !jvmFlags.isEmpty()) {\n      throw new IllegalStateException(\n          \"Failed to construct entrypoint on JavaContainerBuilder; \"\n              + \"jvmFlags were set, but mainClass is null. Specify the main class using \"\n              + \"JavaContainerBuilder#setMainClass(String), or consider using MainClassFinder to \"\n              + \"infer the main class.\");\n    }\n    if (classpathOrder.isEmpty()) {\n      throw new IllegalStateException(\n          \"Failed to construct entrypoint because no files were added to the JavaContainerBuilder\");\n    }\n\n    Map<LayerType, FileEntriesLayer.Builder> layerBuilders = new EnumMap<>(LayerType.class);\n\n    // Add classes to layer configuration\n    for (PathPredicatePair directory : addedClasses) {\n      addDirectoryContentsToLayer(\n          layerBuilders,\n          LayerType.CLASSES,\n          directory.path,\n          directory.predicate,\n          appRoot.resolve(classesDestination));\n    }\n\n    // Add resources to layer configuration\n    for (PathPredicatePair directory : addedResources) {\n      addDirectoryContentsToLayer(\n          layerBuilders,\n          LayerType.RESOURCES,\n          directory.path,\n          directory.predicate,\n          appRoot.resolve(resourcesDestination));\n    }\n\n    // Detect duplicate filenames across all dependency layer types\n    Map<String, Long> occurrences =\n        Streams.concat(\n                addedDependencies.stream(),\n                addedSnapshotDependencies.stream(),\n                addedProjectDependencies.stream())\n            .map(path -> path.getFileName().toString())\n            .collect(Collectors.groupingBy(filename -> filename, Collectors.counting()));\n    List<String> duplicates =\n        occurrences.entrySet().stream()\n            .filter(entry -> entry.getValue() > 1)\n            .map(Map.Entry::getKey)\n            .collect(Collectors.toList());\n\n    ImmutableMap<LayerType, List<Path>> layerMap =\n        ImmutableMap.of(\n            LayerType.DEPENDENCIES, addedDependencies,\n            LayerType.SNAPSHOT_DEPENDENCIES, addedSnapshotDependencies,\n            LayerType.PROJECT_DEPENDENCIES, addedProjectDependencies);\n    for (Map.Entry<LayerType, List<Path>> entry : layerMap.entrySet()) {\n      for (Path file : Preconditions.checkNotNull(entry.getValue())) {\n        // Handle duplicates by appending filesize to the end of the file. This renaming logic\n        // must be in sync with the code that does the same in the other place. See\n        // https://github.com/GoogleContainerTools/jib/issues/3331\n        String jarName = file.getFileName().toString();\n        if (duplicates.contains(jarName)) {\n          jarName = jarName.replaceFirst(\"\\\\.jar$\", \"-\" + Files.size(file)) + \".jar\";\n        }\n        // Add dependencies to layer configuration\n        addFileToLayer(\n            layerBuilders,\n            entry.getKey(),\n            file,\n            appRoot.resolve(dependenciesDestination).resolve(jarName));\n      }\n    }\n\n    // Add others to layer configuration\n    for (Path path : addedOthers) {\n      if (Files.isDirectory(path)) {\n        addDirectoryContentsToLayer(\n            layerBuilders,\n            LayerType.EXTRA_FILES,\n            path,\n            ignored -> true,\n            appRoot.resolve(othersDestination));\n      } else {\n        addFileToLayer(\n            layerBuilders,\n            LayerType.EXTRA_FILES,\n            path,\n            appRoot.resolve(othersDestination).resolve(path.getFileName()));\n      }\n    }\n\n    // Add layer configurations to container builder\n    List<FileEntriesLayer> layers = new ArrayList<>();\n    layerBuilders.forEach((type, builder) -> layers.add(builder.setName(type.getName()).build()));\n    jibContainerBuilder.setFileEntriesLayers(layers);\n\n    if (mainClass != null) {\n      // Construct entrypoint. Ensure classpath elements are in the same order as the files were\n      // added to the JavaContainerBuilder.\n      List<String> classpathElements = new ArrayList<>();\n      for (LayerType path : classpathOrder) {\n        switch (path) {\n          case CLASSES:\n            classpathElements.add(appRoot.resolve(classesDestination).toString());\n            break;\n          case RESOURCES:\n            classpathElements.add(appRoot.resolve(resourcesDestination).toString());\n            break;\n          case DEPENDENCIES:\n            classpathElements.add(appRoot.resolve(dependenciesDestination).resolve(\"*\").toString());\n            break;\n          case EXTRA_FILES:\n            classpathElements.add(appRoot.resolve(othersDestination).toString());\n            break;\n          default:\n            throw new RuntimeException(\n                \"Bug in jib-core; please report the bug at \" + ProjectInfo.GITHUB_NEW_ISSUE_URL);\n        }\n      }\n      String classpathString = String.join(\":\", classpathElements);\n      List<String> entrypoint = new ArrayList<>(4 + jvmFlags.size());\n      entrypoint.add(\"java\");\n      entrypoint.addAll(jvmFlags);\n      entrypoint.add(\"-cp\");\n      entrypoint.add(classpathString);\n      entrypoint.add(mainClass);\n      jibContainerBuilder.setEntrypoint(entrypoint);\n    }\n\n    return jibContainerBuilder;\n  }\n\n  private JavaContainerBuilder addDirectory(\n      List<PathPredicatePair> addedPaths, Path directory, Predicate<Path> filter)\n      throws NoSuchFileException, NotDirectoryException {\n    if (!Files.exists(directory)) {\n      throw new NoSuchFileException(directory.toString());\n    }\n    if (!Files.isDirectory(directory)) {\n      throw new NotDirectoryException(directory.toString());\n    }\n    addedPaths.add(new PathPredicatePair(directory, filter));\n    return this;\n  }\n\n  private void addFileToLayer(\n      Map<LayerType, FileEntriesLayer.Builder> layerBuilders,\n      LayerType layerType,\n      Path sourceFile,\n      AbsoluteUnixPath pathInContainer) {\n    if (!layerBuilders.containsKey(layerType)) {\n      layerBuilders.put(layerType, FileEntriesLayer.builder());\n    }\n    Instant modificationTime = modificationTimeProvider.get(sourceFile, pathInContainer);\n    layerBuilders.get(layerType).addEntry(sourceFile, pathInContainer, modificationTime);\n  }\n\n  private void addDirectoryContentsToLayer(\n      Map<LayerType, FileEntriesLayer.Builder> layerBuilders,\n      LayerType layerType,\n      Path sourceRoot,\n      Predicate<Path> pathFilter,\n      AbsoluteUnixPath basePathInContainer)\n      throws IOException {\n    if (!layerBuilders.containsKey(layerType)) {\n      layerBuilders.put(layerType, FileEntriesLayer.builder());\n    }\n    FileEntriesLayer.Builder builder = layerBuilders.get(layerType);\n\n    new DirectoryWalker(sourceRoot)\n        .filterRoot()\n        .filter(path -> Files.isDirectory(path) || pathFilter.test(path))\n        .walk(\n            path -> {\n              AbsoluteUnixPath pathOnContainer =\n                  basePathInContainer.resolve(sourceRoot.relativize(path));\n              Instant modificationTime = modificationTimeProvider.get(path, pathOnContainer);\n              builder.addEntry(path, pathOnContainer, modificationTime);\n            });\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/Jib.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.nio.file.Paths;\n\n/** Build containers with Jib. */\npublic class Jib {\n\n  public static final String REGISTRY_IMAGE_PREFIX = \"registry://\";\n  public static final String DOCKER_DAEMON_IMAGE_PREFIX = \"docker://\";\n  public static final String TAR_IMAGE_PREFIX = \"tar://\";\n\n  /**\n   * Starts building the container from a base image. The type of base image can be specified using\n   * a prefix, e.g. {@code docker://gcr.io/project/image}. The available prefixes are described\n   * below:\n   *\n   * <ul>\n   *   <li>No prefix, or {@code registry://}: uses a registry base image\n   *   <li>{@code docker://}: uses a base image found in the local Docker daemon\n   *   <li>{@code tar://}: uses a tarball base image at the path following the prefix\n   * </ul>\n   *\n   * @param baseImageReference the base image reference\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   * @throws InvalidImageReferenceException if the {@code baseImageReference} is not a valid image\n   *     reference\n   */\n  public static JibContainerBuilder from(String baseImageReference)\n      throws InvalidImageReferenceException {\n    if (baseImageReference.startsWith(DOCKER_DAEMON_IMAGE_PREFIX)) {\n      return from(\n          DockerDaemonImage.named(baseImageReference.replaceFirst(DOCKER_DAEMON_IMAGE_PREFIX, \"\")));\n    }\n    if (baseImageReference.startsWith(TAR_IMAGE_PREFIX)) {\n      return from(TarImage.at(Paths.get(baseImageReference.replaceFirst(TAR_IMAGE_PREFIX, \"\"))));\n    }\n    return from(RegistryImage.named(baseImageReference.replaceFirst(REGISTRY_IMAGE_PREFIX, \"\")));\n  }\n\n  /**\n   * Starts building the container from a base image. The base image should be publicly-available.\n   * For a base image that requires credentials, use {@link #from(RegistryImage)}.\n   *\n   * @param baseImageReference the base image reference\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   */\n  public static JibContainerBuilder from(ImageReference baseImageReference) {\n    return from(RegistryImage.named(baseImageReference));\n  }\n\n  /**\n   * Starts building the container from a registry base image.\n   *\n   * @param registryImage the {@link RegistryImage} that defines base container registry and\n   *     credentials\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   */\n  public static JibContainerBuilder from(RegistryImage registryImage) {\n    return new JibContainerBuilder(registryImage);\n  }\n\n  /**\n   * Starts building the container from a base image stored in the Docker cache. Requires a running\n   * Docker daemon.\n   *\n   * @param dockerDaemonImage the {@link DockerDaemonImage} that defines the base image and Docker\n   *     client\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   */\n  public static JibContainerBuilder from(DockerDaemonImage dockerDaemonImage) {\n    return new JibContainerBuilder(dockerDaemonImage);\n  }\n\n  /**\n   * Starts building the container from a tarball.\n   *\n   * @param tarImage the {@link TarImage} that defines the path to the base image\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   */\n  public static JibContainerBuilder from(TarImage tarImage) {\n    return new JibContainerBuilder(tarImage);\n  }\n\n  /**\n   * Starts building the container from an empty base image.\n   *\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   */\n  public static JibContainerBuilder fromScratch() {\n    return from(ImageReference.scratch());\n  }\n\n  /**\n   * Starts building the container from a base image stored in the Docker cache. Requires a running\n   * Docker daemon.\n   *\n   * @param dockerClient the {@link DockerClient} to connect\n   * @param dockerDaemonImage the {@link DockerDaemonImage} that defines the base image and Docker\n   *     client\n   * @return a new {@link JibContainerBuilder} to continue building the container\n   */\n  public static JibContainerBuilder from(\n      DockerClient dockerClient, DockerDaemonImage dockerDaemonImage) {\n    return new JibContainerBuilder(dockerClient, dockerDaemonImage);\n  }\n\n  private Jib() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.builder.steps.BuildResult;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.util.Objects;\nimport java.util.Set;\n\n/** The container built by Jib. */\npublic class JibContainer {\n\n  private final ImageReference targetImage;\n  private final DescriptorDigest imageDigest;\n  private final DescriptorDigest imageId;\n  private final Set<String> tags;\n  private final boolean imagePushed;\n\n  @VisibleForTesting\n  JibContainer(\n      ImageReference targetImage,\n      DescriptorDigest imageDigest,\n      DescriptorDigest imageId,\n      Set<String> tags,\n      boolean imagePushed) {\n    this.targetImage = targetImage;\n    this.imageDigest = imageDigest;\n    this.imageId = imageId;\n    this.tags = tags;\n    this.imagePushed = imagePushed;\n  }\n\n  static JibContainer from(BuildContext buildContext, BuildResult buildResult) {\n    ImageReference targetImage = buildContext.getTargetImageConfiguration().getImage();\n    DescriptorDigest imageDigest = buildResult.getImageDigest();\n    DescriptorDigest imageId = buildResult.getImageId();\n    Set<String> tags = buildContext.getAllTargetImageTags();\n    return new JibContainer(targetImage, imageDigest, imageId, tags, buildResult.isImagePushed());\n  }\n\n  /**\n   * Get the target image that was built.\n   *\n   * @return the target image reference.\n   */\n  public ImageReference getTargetImage() {\n    return targetImage;\n  }\n\n  /**\n   * Returns true if we pushed this image all the way to a registry.\n   *\n   * @return true if pushed.\n   */\n  public boolean isImagePushed() {\n    return imagePushed;\n  }\n\n  /**\n   * Gets the digest of the registry image manifest built by Jib. This digest can be used to fetch a\n   * specific image from the registry in the form {@code myregistry/myimage@digest}.\n   *\n   * @return the image digest\n   */\n  public DescriptorDigest getDigest() {\n    return imageDigest;\n  }\n\n  /**\n   * Gets the digest of the container configuration built by Jib.\n   *\n   * @return the image ID\n   */\n  public DescriptorDigest getImageId() {\n    return imageId;\n  }\n\n  /**\n   * Get the tags applied to the container.\n   *\n   * @return the set of all tags\n   */\n  public Set<String> getTags() {\n    return tags;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(targetImage, imageDigest, imageId, tags, imagePushed);\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof JibContainer)) {\n      return false;\n    }\n    JibContainer otherContainer = (JibContainer) other;\n    return targetImage.equals(otherContainer.targetImage)\n        && imageDigest.equals(otherContainer.imageDigest)\n        && imageId.equals(otherContainer.imageId)\n        && tags.equals(otherContainer.tags)\n        && imagePushed == otherContainer.imagePushed;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.LayerObject;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.BuildResult;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport com.google.cloud.tools.jib.docker.DockerClientResolver;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.apache.http.conn.HttpHostConnectException;\n\n/**\n * Builds a container with Jib.\n *\n * <p>Example usage:\n *\n * <pre>{@code\n * Jib.from(baseImage)\n *    .addLayer(sourceFiles, extractionPath)\n *    .setEntrypoint(\"myprogram\", \"--flag\", \"subcommand\")\n *    .setProgramArguments(\"hello\", \"world\")\n *    .addEnvironmentVariable(\"HOME\", \"/app\")\n *    .addExposedPort(Port.tcp(8080))\n *    .addLabel(\"containerizer\", \"jib\")\n *    .containerize(...);\n * }</pre>\n */\npublic class JibContainerBuilder {\n\n  private static String capitalizeFirstLetter(String string) {\n    if (string.isEmpty()) {\n      return string;\n    }\n    return Character.toUpperCase(string.charAt(0)) + string.substring(1);\n  }\n\n  private final ContainerBuildPlan.Builder containerBuildPlanBuilder = ContainerBuildPlan.builder();\n  // TODO(chanseok): remove and use containerBuildPlanBuilder instead. Note that\n  // ContainerConfiguation implements equals() and hashCode(), so need to verify\n  // if they are required.\n  private final ContainerConfiguration.Builder containerConfigurationBuilder =\n      ContainerConfiguration.builder();\n  private final BuildContext.Builder buildContextBuilder;\n\n  private ImageConfiguration baseImageConfiguration;\n  // TODO(chanseok): remove and use containerBuildPlanBuilder instead.\n  private List<FileEntriesLayer> layerConfigurations = new ArrayList<>();\n\n  /** Instantiate with {@link Jib#from}. */\n  JibContainerBuilder(RegistryImage baseImage) {\n    this(\n        ImageConfiguration.builder(baseImage.getImageReference())\n            .setCredentialRetrievers(baseImage.getCredentialRetrievers())\n            .build(),\n        BuildContext.builder());\n  }\n\n  /** Instantiate with {@link Jib#from}. */\n  JibContainerBuilder(DockerDaemonImage baseImage) {\n    this(\n        ImageConfiguration.builder(baseImage.getImageReference())\n            .setDockerClient(\n                DockerClientResolver.resolve(baseImage.getDockerEnvironment())\n                    .orElse(\n                        new CliDockerClient(\n                            baseImage.getDockerExecutable(), baseImage.getDockerEnvironment())))\n            .build(),\n        BuildContext.builder());\n  }\n\n  /** Instantiate with {@link Jib#from}. */\n  JibContainerBuilder(TarImage baseImage) {\n    // TODO: Cleanup using scratch as placeholder\n    this(\n        ImageConfiguration.builder(baseImage.getImageReference().orElse(ImageReference.scratch()))\n            .setTarPath(baseImage.getPath())\n            .build(),\n        BuildContext.builder());\n  }\n\n  /** Instantiate with {@link Jib#from}. */\n  JibContainerBuilder(DockerClient dockerClient, DockerDaemonImage baseImage) {\n    this(\n        ImageConfiguration.builder(baseImage.getImageReference())\n            .setDockerClient(dockerClient)\n            .build(),\n        BuildContext.builder());\n  }\n\n  @VisibleForTesting\n  JibContainerBuilder(\n      ImageConfiguration imageConfiguration, BuildContext.Builder buildContextBuilder) {\n    this.buildContextBuilder = buildContextBuilder.setBaseImageConfiguration(imageConfiguration);\n    baseImageConfiguration = imageConfiguration;\n    containerBuildPlanBuilder.setBaseImage(imageConfiguration.getImage().toString());\n  }\n\n  /**\n   * Adds a new layer to the container with {@code files} as the source files and {@code\n   * pathInContainer} as the path to copy the source files to in the container file system.\n   *\n   * <p>Source files that are directories will be recursively copied. For example, if the source\n   * files are:\n   *\n   * <ul>\n   *   <li>{@code fileFoo}\n   *   <li>{@code fileBar}\n   *   <li>{@code directory/}\n   * </ul>\n   *\n   * <p>and the destination to copy to is {@code /path/in/container}, then the new layer will have\n   * the following entries for the container file system:\n   *\n   * <ul>\n   *   <li>{@code /path/in/container/fileFoo}\n   *   <li>{@code /path/in/container/fileBar}\n   *   <li>{@code /path/in/container/directory/}\n   *   <li>{@code /path/in/container/directory/...} (all contents of {@code directory/})\n   * </ul>\n   *\n   * @param files the source files to copy to a new layer in the container\n   * @param pathInContainer the path in the container file system corresponding to the {@code\n   *     sourceFile}\n   * @return this\n   * @throws IOException if an exception occurred when recursively listing any directories\n   */\n  public JibContainerBuilder addLayer(List<Path> files, AbsoluteUnixPath pathInContainer)\n      throws IOException {\n    FileEntriesLayer.Builder layerConfigurationBuilder = FileEntriesLayer.builder();\n\n    for (Path file : files) {\n      layerConfigurationBuilder.addEntryRecursive(\n          file, pathInContainer.resolve(file.getFileName()));\n    }\n\n    return addFileEntriesLayer(layerConfigurationBuilder.build());\n  }\n\n  /**\n   * Adds a new layer to the container with {@code files} as the source files and {@code\n   * pathInContainer} as the path to copy the source files to in the container file system.\n   *\n   * @param files the source files to copy to a new layer in the container\n   * @param pathInContainer the path in the container file system corresponding to the {@code\n   *     sourceFile}\n   * @return this\n   * @throws IOException if an exception occurred when recursively listing any directories\n   * @throws IllegalArgumentException if {@code pathInContainer} is not an absolute Unix-style path\n   * @see #addLayer(List, AbsoluteUnixPath)\n   */\n  public JibContainerBuilder addLayer(List<Path> files, String pathInContainer) throws IOException {\n    return addLayer(files, AbsoluteUnixPath.get(pathInContainer));\n  }\n\n  /**\n   * Adds a layer (defined by a {@link LayerConfiguration}).\n   *\n   * @deprecated use {@link #addFileEntriesLayer(FileEntriesLayer)}.\n   * @param layerConfiguration the {@link LayerConfiguration}\n   * @return this\n   */\n  @Deprecated\n  public JibContainerBuilder addLayer(LayerConfiguration layerConfiguration) {\n    return addFileEntriesLayer(layerConfiguration.toFileEntriesLayer());\n  }\n\n  /**\n   * Adds a layer (defined by a {@link FileEntriesLayer}).\n   *\n   * @param layer the {@link FileEntriesLayer}\n   * @return this\n   */\n  public JibContainerBuilder addFileEntriesLayer(FileEntriesLayer layer) {\n    containerBuildPlanBuilder.addLayer(layer);\n    layerConfigurations.add(layer);\n    return this;\n  }\n\n  /**\n   * Sets the layers (defined by a list of {@link LayerConfiguration}s). This replaces any\n   * previously-added layers.\n   *\n   * @deprecated use {@link #setFileEntriesLayers(List)}.\n   * @param layerConfigurations the list of {@link LayerConfiguration}s\n   * @return this\n   */\n  @Deprecated\n  public JibContainerBuilder setLayers(List<LayerConfiguration> layerConfigurations) {\n    return setFileEntriesLayers(\n        layerConfigurations.stream()\n            .map(LayerConfiguration::toFileEntriesLayer)\n            .collect(Collectors.toList()));\n  }\n\n  /**\n   * Sets the layers (defined by a list of {@link FileEntriesLayer}s). This replaces any\n   * previously-added layers.\n   *\n   * @param layers the list of {@link FileEntriesLayer}s\n   * @return this\n   */\n  public JibContainerBuilder setFileEntriesLayers(List<FileEntriesLayer> layers) {\n    containerBuildPlanBuilder.setLayers(layers);\n    layerConfigurations = new ArrayList<>(layers);\n    return this;\n  }\n\n  /**\n   * Sets the layers. This replaces any previously-added layers.\n   *\n   * @deprecated use {@link #setFileEntriesLayers(FileEntriesLayer...)}.\n   * @param layerConfigurations the {@link LayerConfiguration}s\n   * @return this\n   */\n  @Deprecated\n  public JibContainerBuilder setLayers(LayerConfiguration... layerConfigurations) {\n    return setLayers(Arrays.asList(layerConfigurations));\n  }\n\n  /**\n   * Sets the layers. This replaces any previously-added layers.\n   *\n   * @param layers the {@link FileEntriesLayer}s\n   * @return this\n   */\n  public JibContainerBuilder setFileEntriesLayers(FileEntriesLayer... layers) {\n    return setFileEntriesLayers(Arrays.asList(layers));\n  }\n\n  /**\n   * Sets the container entrypoint. This is the beginning of the command that is run when the\n   * container starts. {@link #setProgramArguments} sets additional tokens.\n   *\n   * <p>This is similar to <a\n   * href=\"https://docs.docker.com/engine/reference/builder/#exec-form-entrypoint-example\">{@code\n   * ENTRYPOINT} in Dockerfiles</a> or {@code command} in the <a\n   * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n   * Container spec</a>.\n   *\n   * @param entrypoint a list of the entrypoint command\n   * @return this\n   */\n  public JibContainerBuilder setEntrypoint(@Nullable List<String> entrypoint) {\n    containerBuildPlanBuilder.setEntrypoint(entrypoint);\n    containerConfigurationBuilder.setEntrypoint(entrypoint);\n    return this;\n  }\n\n  /**\n   * Sets the container entrypoint.\n   *\n   * @param entrypoint the entrypoint command\n   * @return this\n   * @see #setEntrypoint(List)\n   */\n  public JibContainerBuilder setEntrypoint(String... entrypoint) {\n    return setEntrypoint(Arrays.asList(entrypoint));\n  }\n\n  /**\n   * Sets the container entrypoint program arguments. These are additional tokens added to the end\n   * of the entrypoint command.\n   *\n   * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#cmd\">{@code\n   * CMD} in Dockerfiles</a> or {@code args} in the <a\n   * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n   * Container spec</a>.\n   *\n   * <p>For example, if the entrypoint was {@code myprogram --flag subcommand} and program arguments\n   * were {@code hello world}, then the command that run when the container starts is {@code\n   * myprogram --flag subcommand hello world}.\n   *\n   * @param programArguments a list of program argument tokens\n   * @return this\n   */\n  public JibContainerBuilder setProgramArguments(@Nullable List<String> programArguments) {\n    containerBuildPlanBuilder.setCmd(programArguments);\n    containerConfigurationBuilder.setProgramArguments(programArguments);\n    return this;\n  }\n\n  /**\n   * Sets the container entrypoint program arguments.\n   *\n   * @param programArguments program arguments tokens\n   * @return this\n   * @see #setProgramArguments(List)\n   */\n  public JibContainerBuilder setProgramArguments(String... programArguments) {\n    return setProgramArguments(Arrays.asList(programArguments));\n  }\n\n  /**\n   * Sets the container environment. These environment variables are available to the program\n   * launched by the container entrypoint command. This replaces any previously-set environment\n   * variables.\n   *\n   * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#env\">{@code\n   * ENV} in Dockerfiles</a> or {@code env} in the <a\n   * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n   * Container spec</a>.\n   *\n   * @param environmentMap a map of environment variable names to values\n   * @return this\n   */\n  public JibContainerBuilder setEnvironment(Map<String, String> environmentMap) {\n    containerBuildPlanBuilder.setEnvironment(environmentMap);\n    containerConfigurationBuilder.setEnvironment(environmentMap);\n    return this;\n  }\n\n  /**\n   * Adds a variable in the container environment.\n   *\n   * @param name the environment variable name\n   * @param value the environment variable value\n   * @return this\n   * @see #setEnvironment\n   */\n  public JibContainerBuilder addEnvironmentVariable(String name, String value) {\n    containerBuildPlanBuilder.addEnvironmentVariable(name, value);\n    containerConfigurationBuilder.addEnvironment(name, value);\n    return this;\n  }\n\n  /**\n   * Sets the directories that may hold externally mounted volumes.\n   *\n   * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#volume\">{@code\n   * VOLUME} in Dockerfiles</a>.\n   *\n   * @param volumes the directory paths on the container filesystem to set as volumes\n   * @return this\n   */\n  public JibContainerBuilder setVolumes(Set<AbsoluteUnixPath> volumes) {\n    containerBuildPlanBuilder.setVolumes(volumes);\n    containerConfigurationBuilder.setVolumes(volumes);\n    return this;\n  }\n\n  /**\n   * Sets the directories that may hold externally mounted volumes.\n   *\n   * @param volumes the directory paths on the container filesystem to set as volumes\n   * @return this\n   * @see #setVolumes(Set)\n   */\n  public JibContainerBuilder setVolumes(AbsoluteUnixPath... volumes) {\n    return setVolumes(new HashSet<>(Arrays.asList(volumes)));\n  }\n\n  /**\n   * Adds a directory that may hold an externally mounted volume.\n   *\n   * @param volume a directory path on the container filesystem to represent a volume\n   * @return this\n   * @see #setVolumes(Set)\n   */\n  public JibContainerBuilder addVolume(AbsoluteUnixPath volume) {\n    containerBuildPlanBuilder.addVolume(volume);\n    containerConfigurationBuilder.addVolume(volume);\n    return this;\n  }\n\n  /**\n   * Sets the ports to expose from the container. Ports exposed will allow ingress traffic. This\n   * replaces any previously-set exposed ports.\n   *\n   * <p>Use {@link Port#tcp} to expose a port for TCP traffic and {@link Port#udp} to expose a port\n   * for UDP traffic.\n   *\n   * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#expose\">{@code\n   * EXPOSE} in Dockerfiles</a> or {@code ports} in the <a\n   * href=\"https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#container-v1-core\">Kubernetes\n   * Container spec</a>.\n   *\n   * @param ports the ports to expose\n   * @return this\n   */\n  public JibContainerBuilder setExposedPorts(Set<Port> ports) {\n    containerBuildPlanBuilder.setExposedPorts(ports);\n    containerConfigurationBuilder.setExposedPorts(ports);\n    return this;\n  }\n\n  /**\n   * Sets the ports to expose from the container. This replaces any previously-set exposed ports.\n   *\n   * @param ports the ports to expose\n   * @return this\n   * @see #setExposedPorts(Set)\n   */\n  public JibContainerBuilder setExposedPorts(Port... ports) {\n    return setExposedPorts(new HashSet<>(Arrays.asList(ports)));\n  }\n\n  /**\n   * Adds a port to expose from the container.\n   *\n   * @param port the port to expose\n   * @return this\n   * @see #setExposedPorts(Set)\n   */\n  public JibContainerBuilder addExposedPort(Port port) {\n    containerBuildPlanBuilder.addExposedPort(port);\n    containerConfigurationBuilder.addExposedPort(port);\n    return this;\n  }\n\n  /**\n   * Sets the labels for the container. This replaces any previously-set labels.\n   *\n   * <p>This is similar to <a href=\"https://docs.docker.com/engine/reference/builder/#label\">{@code\n   * LABEL} in Dockerfiles</a>.\n   *\n   * @param labelMap a map of label keys to values\n   * @return this\n   */\n  public JibContainerBuilder setLabels(Map<String, String> labelMap) {\n    containerBuildPlanBuilder.setLabels(labelMap);\n    containerConfigurationBuilder.setLabels(labelMap);\n    return this;\n  }\n\n  /**\n   * Sets a label for the container.\n   *\n   * @param key the label key\n   * @param value the label value\n   * @return this\n   */\n  public JibContainerBuilder addLabel(String key, String value) {\n    containerBuildPlanBuilder.addLabel(key, value);\n    containerConfigurationBuilder.addLabel(key, value);\n    return this;\n  }\n\n  /**\n   * Sets the format to build the container image as. Use {@link ImageFormat#Docker} for Docker V2.2\n   * or {@link ImageFormat#OCI} for OCI.\n   *\n   * @param imageFormat the {@link ImageFormat}\n   * @return this\n   */\n  public JibContainerBuilder setFormat(ImageFormat imageFormat) {\n    containerBuildPlanBuilder.setFormat(imageFormat);\n    buildContextBuilder.setTargetFormat(imageFormat);\n    return this;\n  }\n\n  /**\n   * Sets the container image creation time. The default is {@link Instant#EPOCH}.\n   *\n   * @param creationTime the container image creation time\n   * @return this\n   */\n  public JibContainerBuilder setCreationTime(Instant creationTime) {\n    containerBuildPlanBuilder.setCreationTime(creationTime);\n    containerConfigurationBuilder.setCreationTime(creationTime);\n    return this;\n  }\n\n  /**\n   * Sets a desired platform (properties including OS and architecture) list. If the base image\n   * reference is a Docker manifest list or an OCI image index, an image builder may select the base\n   * images matching the given platforms. If the base image reference is an image manifest, an image\n   * builder may ignore the given platforms and use the platform of the base image or may decide to\n   * raise on error.\n   *\n   * <p>Note that a new container builder starts with \"amd64/linux\" as the default platform.\n   *\n   * @param platforms list of platforms to select base images in case of a manifest list\n   * @return this\n   */\n  public JibContainerBuilder setPlatforms(Set<Platform> platforms) {\n    containerBuildPlanBuilder.setPlatforms(platforms);\n    containerConfigurationBuilder.setPlatforms(platforms);\n    return this;\n  }\n\n  /**\n   * Adds a desired image platform (OS and architecture pair). If the base image reference is a\n   * Docker manifest list or an OCI image index, an image builder may select the base image matching\n   * the given platform. If the base image reference is an image manifest, an image builder may\n   * ignore the given platform and use the platform of the base image or may decide to raise on\n   * error.\n   *\n   * <p>Note that a new new container builder starts with \"amd64/linux\" as the default platform. If\n   * you want to reset the default platform instead of adding a new one, use {@link\n   * #setPlatforms(Set)}.\n   *\n   * @param architecture architecture (for example, {@code amd64}) to select a base image in case of\n   *     a manifest list\n   * @param os OS (for example, {@code linux}) to select a base image in case of a manifest list\n   * @return this\n   */\n  public JibContainerBuilder addPlatform(String architecture, String os) {\n    containerBuildPlanBuilder.addPlatform(architecture, os);\n    containerConfigurationBuilder.addPlatform(architecture, os);\n    return this;\n  }\n\n  /**\n   * Sets the user and group to run the container as. {@code user} can be a username or UID along\n   * with an optional groupname or GID.\n   *\n   * <p>The following are valid formats for {@code user}\n   *\n   * <ul>\n   *   <li>{@code user}\n   *   <li>{@code uid}\n   *   <li>{@code :group}\n   *   <li>{@code :gid}\n   *   <li>{@code user:group}\n   *   <li>{@code uid:gid}\n   *   <li>{@code uid:group}\n   *   <li>{@code user:gid}\n   * </ul>\n   *\n   * @param user the user to run the container as\n   * @return this\n   */\n  public JibContainerBuilder setUser(@Nullable String user) {\n    containerBuildPlanBuilder.setUser(user);\n    containerConfigurationBuilder.setUser(user);\n    return this;\n  }\n\n  /**\n   * Sets the working directory in the container.\n   *\n   * @param workingDirectory the working directory\n   * @return this\n   */\n  public JibContainerBuilder setWorkingDirectory(@Nullable AbsoluteUnixPath workingDirectory) {\n    containerBuildPlanBuilder.setWorkingDirectory(workingDirectory);\n    containerConfigurationBuilder.setWorkingDirectory(workingDirectory);\n    return this;\n  }\n\n  /**\n   * Builds the container.\n   *\n   * @param containerizer the {@link Containerizer} that configures how to containerize\n   * @return the built container\n   * @throws IOException if an I/O exception occurs\n   * @throws CacheDirectoryCreationException if a directory to be used for the cache could not be\n   *     created\n   * @throws HttpHostConnectException if jib failed to connect to a registry\n   * @throws RegistryUnauthorizedException if a registry request is unauthorized and needs\n   *     authentication\n   * @throws RegistryAuthenticationFailedException if registry authentication failed\n   * @throws UnknownHostException if the registry does not exist\n   * @throws InsecureRegistryException if a server could not be verified due to an insecure\n   *     connection\n   * @throws RegistryException if some other error occurred while interacting with a registry\n   * @throws ExecutionException if some other exception occurred during execution\n   * @throws InterruptedException if the execution was interrupted\n   */\n  public JibContainer containerize(Containerizer containerizer)\n      throws InterruptedException, RegistryException, IOException, CacheDirectoryCreationException,\n          ExecutionException {\n    try (BuildContext buildContext = toBuildContext(containerizer);\n        TimerEventDispatcher ignored =\n            new TimerEventDispatcher(\n                buildContext.getEventHandlers(), containerizer.getDescription())) {\n      logSources(buildContext.getEventHandlers());\n\n      BuildResult buildResult = containerizer.run(buildContext);\n      return JibContainer.from(buildContext, buildResult);\n\n    } catch (ExecutionException ex) {\n      // If an ExecutionException occurs, re-throw the cause to be more easily handled by the user\n      if (ex.getCause() instanceof RegistryException) {\n        throw (RegistryException) ex.getCause();\n      }\n      throw ex;\n    }\n  }\n\n  /**\n   * Describes the container contents and configuration without actually physically building a\n   * container.\n   *\n   * @deprecated use {@link #toContainerBuildPlan}.\n   * @return a description of the container being built\n   */\n  @Deprecated\n  public JibContainerDescription describeContainer() {\n    return new JibContainerDescription(layerConfigurations);\n  }\n\n  /**\n   * Internal method. API end users should not use it.\n   *\n   * <p>Converts to {@link ContainerBuildPlan}. Note that not all values that this class holds can\n   * be described by a build plan, such as {@link CredentialRetriever}s for {@link RegistryImage},\n   * {@link DockerClient} for {@link DockerDaemonImage}, and output path for {@link TarImage}.\n   *\n   * @return {@link ContainerBuildPlan}\n   */\n  public ContainerBuildPlan toContainerBuildPlan() {\n    return containerBuildPlanBuilder.build();\n  }\n\n  /**\n   * Internal method. API end users should not use it.\n   *\n   * <p>Reconfigures {@link JibContainerBuilder} from the given {@code buildPlan}. Every value\n   * configurable using \"setters\" in this class is overwritten by the value in {@code buildPlan};\n   * only retained are some base image properties inherent in {@link JibContainerBuilder} but absent\n   * in {@link ContainerBuildPlan}, such as {@link CredentialRetriever}s for {@link RegistryImage},\n   * {@link DockerClient} for {@link DockerDaemonImage}, and output path for {@link TarImage}.\n   *\n   * @param buildPlan build plan to apply\n   * @return {@link JibContainerBuilder} reconfigured from {@code buildPlan}\n   * @throws InvalidImageReferenceException if the base image value in {@code buildPlan} is an\n   *     invalid reference\n   */\n  public JibContainerBuilder applyContainerBuildPlan(ContainerBuildPlan buildPlan)\n      throws InvalidImageReferenceException {\n    containerBuildPlanBuilder\n        .setBaseImage(buildPlan.getBaseImage())\n        .setPlatforms(buildPlan.getPlatforms())\n        .setCreationTime(buildPlan.getCreationTime())\n        .setFormat(buildPlan.getFormat())\n        .setEnvironment(buildPlan.getEnvironment())\n        .setLabels(buildPlan.getLabels())\n        .setVolumes(buildPlan.getVolumes())\n        .setExposedPorts(buildPlan.getExposedPorts())\n        .setUser(buildPlan.getUser())\n        .setWorkingDirectory(buildPlan.getWorkingDirectory())\n        .setEntrypoint(buildPlan.getEntrypoint())\n        .setCmd(buildPlan.getCmd())\n        .setLayers(buildPlan.getLayers());\n\n    containerConfigurationBuilder\n        .setPlatforms(buildPlan.getPlatforms())\n        .setCreationTime(buildPlan.getCreationTime())\n        .setEnvironment(buildPlan.getEnvironment())\n        .setLabels(buildPlan.getLabels())\n        .setVolumes(buildPlan.getVolumes())\n        .setExposedPorts(buildPlan.getExposedPorts())\n        .setUser(buildPlan.getUser())\n        .setWorkingDirectory(buildPlan.getWorkingDirectory())\n        .setEntrypoint(buildPlan.getEntrypoint())\n        .setProgramArguments(buildPlan.getCmd());\n\n    ImageConfiguration.Builder builder =\n        ImageConfiguration.builder(ImageReference.parse(buildPlan.getBaseImage()))\n            .setCredentialRetrievers(baseImageConfiguration.getCredentialRetrievers());\n    baseImageConfiguration.getDockerClient().ifPresent(builder::setDockerClient);\n    baseImageConfiguration.getTarPath().ifPresent(builder::setTarPath);\n    baseImageConfiguration = builder.build();\n\n    // For now, only FileEntriesLayer is supported in jib-core.\n    Function<LayerObject, FileEntriesLayer> castToFileEntriesLayer =\n        layer -> {\n          Verify.verify(\n              layer instanceof FileEntriesLayer,\n              \"layer types other than FileEntriesLayer not yet supported in build plan layers\");\n          return (FileEntriesLayer) layer;\n        };\n    layerConfigurations =\n        buildPlan.getLayers().stream().map(castToFileEntriesLayer).collect(Collectors.toList());\n\n    buildContextBuilder\n        .setTargetFormat(buildPlan.getFormat())\n        .setBaseImageConfiguration(baseImageConfiguration)\n        .setLayerConfigurations(layerConfigurations);\n    return this;\n  }\n\n  /**\n   * Builds a {@link BuildContext} using this and a {@link Containerizer}.\n   *\n   * @param containerizer the {@link Containerizer}\n   * @return the {@link BuildContext}\n   * @throws CacheDirectoryCreationException if a cache directory could not be created\n   */\n  @VisibleForTesting\n  BuildContext toBuildContext(Containerizer containerizer) throws CacheDirectoryCreationException {\n    return buildContextBuilder\n        .setTargetImageConfiguration(containerizer.getImageConfiguration())\n        .setAdditionalTargetImageTags(containerizer.getAdditionalTags())\n        .setBaseImageLayersCacheDirectory(containerizer.getBaseImageLayersCacheDirectory())\n        .setApplicationLayersCacheDirectory(containerizer.getApplicationLayersCacheDirectory())\n        .setContainerConfiguration(containerConfigurationBuilder.build())\n        .setLayerConfigurations(layerConfigurations)\n        .setAllowInsecureRegistries(containerizer.getAllowInsecureRegistries())\n        .setOffline(containerizer.isOfflineMode())\n        .setToolName(containerizer.getToolName())\n        .setToolVersion(containerizer.getToolVersion())\n        .setExecutorService(containerizer.getExecutorService().orElse(null))\n        .setEventHandlers(containerizer.buildEventHandlers())\n        .setAlwaysCacheBaseImage(containerizer.getAlwaysCacheBaseImage())\n        .setRegistryMirrors(containerizer.getRegistryMirrors())\n        .build();\n  }\n\n  private void logSources(EventHandlers eventHandlers) {\n    // Logs the different source files used.\n    eventHandlers.dispatch(LogEvent.info(\"Containerizing application with the following files:\"));\n\n    for (FileEntriesLayer layer : layerConfigurations) {\n      if (layer.getEntries().isEmpty()) {\n        continue;\n      }\n\n      eventHandlers.dispatch(LogEvent.info(\"\\t\" + capitalizeFirstLetter(layer.getName()) + \":\"));\n\n      for (FileEntry entry : layer.getEntries()) {\n        eventHandlers.dispatch(LogEvent.info(\"\\t\\t\" + entry.getSourceFile()));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerDescription.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.common.collect.ImmutableList;\nimport java.util.List;\n\n/**\n * A class containing the representation of the contents of a container. Currently only exposes\n * \"layers\", but can be extended to expose {@link ContainerConfiguration}, {@link ImageReference} of\n * the base image, or other informational classes.\n *\n * <p>This class is immutable and thread-safe.\n */\npublic class JibContainerDescription {\n\n  private final ImmutableList<FileEntriesLayer> layers;\n\n  JibContainerDescription(List<FileEntriesLayer> layers) {\n    this.layers = ImmutableList.copyOf(layers);\n  }\n\n  /**\n   * Returns a list of \"user configured\" layers, does <em>not</em> include base layer information.\n   *\n   * @return a {@link List} of {@link FileEntriesLayer}s\n   */\n  public List<FileEntriesLayer> getFileEntriesLayers() {\n    return layers;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/JibEvent.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\n/**\n * Type for events dispatched by Jib Core. Implementation classes should <b>not</b> inherit from\n * each other.\n */\npublic interface JibEvent {}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissionsProvider;\nimport com.google.cloud.tools.jib.api.buildplan.ModificationTimeProvider;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Configures how to build a layer in the container image. Instantiate with {@link #builder}.\n *\n * @deprecated Use {@link FileEntriesLayer}.\n */\n@Deprecated\n@Immutable\npublic class LayerConfiguration {\n\n  /** Builds a {@link LayerConfiguration}. */\n  public static class Builder {\n\n    private FileEntriesLayer.Builder layerBuilder = FileEntriesLayer.builder();\n\n    private Builder() {}\n\n    /**\n     * Sets a name for this layer. This name does not affect the contents of the layer.\n     *\n     * @param name the name\n     * @return this\n     */\n    public Builder setName(String name) {\n      layerBuilder.setName(name);\n      return this;\n    }\n\n    /**\n     * Sets entries for the layer.\n     *\n     * @param entries file entries in the layer\n     * @return this\n     */\n    public Builder setEntries(List<LayerEntry> entries) {\n      layerBuilder.setEntries(\n          entries.stream().map(LayerEntry::toFileEntry).collect(Collectors.toList()));\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer.\n     *\n     * @param entry the layer entry to add\n     * @return this\n     */\n    public Builder addEntry(LayerEntry entry) {\n      layerBuilder.addEntry(entry.toFileEntry());\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer. Only adds the single source file to the exact path in the\n     * container file system.\n     *\n     * <p>For example, {@code addEntry(Paths.get(\"myfile\"),\n     * AbsoluteUnixPath.get(\"/path/in/container\"))} adds a file {@code myfile} to the container file\n     * system at {@code /path/in/container}.\n     *\n     * <p>For example, {@code addEntry(Paths.get(\"mydirectory\"),\n     * AbsoluteUnixPath.get(\"/path/in/container\"))} adds a directory {@code mydirectory/} to the\n     * container file system at {@code /path/in/container/}. This does <b>not</b> add the contents\n     * of {@code mydirectory}.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @return this\n     */\n    public Builder addEntry(Path sourceFile, AbsoluteUnixPath pathInContainer) {\n      layerBuilder.addEntry(sourceFile, pathInContainer);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer with the given permissions. Only adds the single source file to\n     * the exact path in the container file system. See {@link Builder#addEntry(Path,\n     * AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param permissions the file permissions on the container\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     * @see FilePermissions#DEFAULT_FILE_PERMISSIONS\n     * @see FilePermissions#DEFAULT_FOLDER_PERMISSIONS\n     */\n    public Builder addEntry(\n        Path sourceFile, AbsoluteUnixPath pathInContainer, FilePermissions permissions) {\n      layerBuilder.addEntry(sourceFile, pathInContainer, permissions);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer with the given file modification time. Only adds the single source\n     * file to the exact path in the container file system. See {@link Builder#addEntry(Path,\n     * AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param modificationTime the file modification time\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     */\n    public Builder addEntry(\n        Path sourceFile, AbsoluteUnixPath pathInContainer, Instant modificationTime) {\n      layerBuilder.addEntry(sourceFile, pathInContainer, modificationTime);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer with the given permissions and file modification time. Only adds\n     * the single source file to the exact path in the container file system. See {@link\n     * Builder#addEntry(Path, AbsoluteUnixPath)} for more information.\n     *\n     * @param sourceFile the source file to add to the layer\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param permissions the file permissions on the container\n     * @param modificationTime the file modification time\n     * @return this\n     * @see Builder#addEntry(Path, AbsoluteUnixPath)\n     * @see FilePermissions#DEFAULT_FILE_PERMISSIONS\n     * @see FilePermissions#DEFAULT_FOLDER_PERMISSIONS\n     */\n    public Builder addEntry(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissions permissions,\n        Instant modificationTime) {\n      layerBuilder.addEntry(\n          new FileEntry(sourceFile, pathInContainer, permissions, modificationTime));\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * <p>For example, {@code addEntryRecursive(Paths.get(\"mydirectory\",\n     * AbsoluteUnixPath.get(\"/path/in/container\"))} adds {@code mydirectory} to the container file\n     * system at {@code /path/in/container} such that {@code mydirectory/subfile} is found at {@code\n     * /path/in/container/subfile}.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(Path sourceFile, AbsoluteUnixPath pathInContainer)\n        throws IOException {\n      layerBuilder.addEntryRecursive(sourceFile, pathInContainer);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param filePermissionProvider a provider that takes a source path and destination path on the\n     *     container and returns the file permissions that should be set for that path\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissionsProvider filePermissionProvider)\n        throws IOException {\n      layerBuilder.addEntryRecursive(sourceFile, pathInContainer, filePermissionProvider);\n      return this;\n    }\n\n    /**\n     * Adds an entry to the layer. If the source file is a directory, the directory and its contents\n     * will be added recursively.\n     *\n     * @param sourceFile the source file to add to the layer recursively\n     * @param pathInContainer the path in the container file system corresponding to the {@code\n     *     sourceFile}\n     * @param filePermissionProvider a provider that takes a source path and destination path on the\n     *     container and returns the file permissions that should be set for that path\n     * @param modificationTimeProvider a provider that takes a source path and destination path on\n     *     the container and returns the file modification time that should be set for that path\n     * @return this\n     * @throws IOException if an exception occurred when recursively listing the directory\n     */\n    public Builder addEntryRecursive(\n        Path sourceFile,\n        AbsoluteUnixPath pathInContainer,\n        FilePermissionsProvider filePermissionProvider,\n        ModificationTimeProvider modificationTimeProvider)\n        throws IOException {\n      layerBuilder.addEntryRecursive(\n          sourceFile, pathInContainer, filePermissionProvider, modificationTimeProvider);\n      return this;\n    }\n\n    /**\n     * Returns the built {@link LayerConfiguration}.\n     *\n     * @return the built {@link LayerConfiguration}\n     */\n    public LayerConfiguration build() {\n      return new LayerConfiguration(layerBuilder.build());\n    }\n  }\n\n  /** Provider that returns default file permissions (644 for files, 755 for directories). */\n  public static final FilePermissionsProvider DEFAULT_FILE_PERMISSIONS_PROVIDER =\n      FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER;\n\n  /** Default file modification time (EPOCH + 1 second). */\n  public static final Instant DEFAULT_MODIFICATION_TIME =\n      FileEntriesLayer.DEFAULT_MODIFICATION_TIME;\n\n  /** Provider that returns default file modification time (EPOCH + 1 second). */\n  public static final ModificationTimeProvider DEFAULT_MODIFICATION_TIME_PROVIDER =\n      FileEntriesLayer.DEFAULT_MODIFICATION_TIME_PROVIDER;\n\n  /**\n   * Gets a new {@link Builder} for {@link LayerConfiguration}.\n   *\n   * @return a new {@link Builder}\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private final FileEntriesLayer fileEntriesLayer;\n\n  private LayerConfiguration(FileEntriesLayer fileEntriesLayer) {\n    this.fileEntriesLayer = fileEntriesLayer;\n  }\n\n  /**\n   * Gets the name.\n   *\n   * @return the name\n   */\n  public String getName() {\n    return fileEntriesLayer.getName();\n  }\n\n  /**\n   * Gets the list of entries.\n   *\n   * @return the list of entries\n   */\n  public ImmutableList<LayerEntry> getLayerEntries() {\n    List<FileEntry> entries = fileEntriesLayer.getEntries();\n    return entries.stream().map(LayerEntry::new).collect(ImmutableList.toImmutableList());\n  }\n\n  FileEntriesLayer toFileEntriesLayer() {\n    return fileEntriesLayer;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/LayerEntry.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport java.nio.file.Path;\nimport java.time.Instant;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Represents an entry in the layer. A layer consists of many entries that can be converted into tar\n * archive entries.\n *\n * <p>This class is immutable and thread-safe.\n *\n * @deprecated Use {@link FileEntry}.\n */\n@Deprecated\n@Immutable\npublic class LayerEntry {\n\n  private final FileEntry fileEntry;\n\n  /**\n   * Instantiates with a source file and the path to place the source file in the container file\n   * system.\n   *\n   * <p>For example, {@code new LayerEntry(Paths.get(\"HelloWorld.class\"),\n   * AbsoluteUnixPath.get(\"/app/classes/HelloWorld.class\"))} adds a file {@code HelloWorld.class} to\n   * the container file system at {@code /app/classes/HelloWorld.class}.\n   *\n   * <p>For example, {@code new LayerEntry(Paths.get(\"com\"),\n   * AbsoluteUnixPath.get(\"/app/classes/com\"))} adds a directory to the container file system at\n   * {@code /app/classes/com}. This does <b>not</b> add the contents of {@code com/}.\n   *\n   * <p>Note that:\n   *\n   * <ul>\n   *   <li>Entry source files can be either files or directories.\n   *   <li>Adding a directory does not include the contents of the directory. Each file under a\n   *       directory must be added as a separate {@link LayerEntry}.\n   * </ul>\n   *\n   * @param sourceFile the source file to add to the layer\n   * @param extractionPath the path in the container file system corresponding to the {@code\n   *     sourceFile}\n   * @param permissions the file permissions on the container\n   * @param modificationTime the file modification time\n   */\n  public LayerEntry(\n      Path sourceFile,\n      AbsoluteUnixPath extractionPath,\n      FilePermissions permissions,\n      Instant modificationTime) {\n    this(new FileEntry(sourceFile, extractionPath, permissions, modificationTime));\n  }\n\n  LayerEntry(FileEntry entry) {\n    fileEntry = entry;\n  }\n\n  /**\n   * Returns the modification time of the file in the entry.\n   *\n   * @return the modification time\n   */\n  public Instant getModificationTime() {\n    return fileEntry.getModificationTime();\n  }\n\n  /**\n   * Gets the source file. The source file may be relative or absolute, so the caller should use\n   * {@code getSourceFile().toAbsolutePath().toString()} for the serialized form since the\n   * serialization could change independently of the path representation.\n   *\n   * @return the source file\n   */\n  public Path getSourceFile() {\n    return fileEntry.getSourceFile();\n  }\n\n  /**\n   * Gets the extraction path.\n   *\n   * @return the extraction path\n   */\n  public AbsoluteUnixPath getExtractionPath() {\n    return fileEntry.getExtractionPath();\n  }\n\n  /**\n   * Gets the file permissions on the container.\n   *\n   * @return the file permissions on the container\n   */\n  public FilePermissions getPermissions() {\n    return fileEntry.getPermissions();\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof LayerEntry)) {\n      return false;\n    }\n    LayerEntry otherLayerEntry = (LayerEntry) other;\n    return toFileEntry().equals(otherLayerEntry.toFileEntry());\n  }\n\n  @Override\n  public int hashCode() {\n    return fileEntry.hashCode();\n  }\n\n  FileEntry toFileEntry() {\n    return fileEntry;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/LogEvent.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.util.Objects;\n\n/** Log message event. */\npublic class LogEvent implements JibEvent {\n\n  /** Log levels, in order of verbosity. */\n  public enum Level {\n\n    /** Something went wrong. */\n    ERROR,\n\n    /** Something might not work as intended. */\n    WARN,\n\n    /** Default. */\n    LIFECYCLE,\n\n    /** Same as {@link #LIFECYCLE}, except represents progress updates. */\n    PROGRESS,\n\n    /**\n     * Details that can be ignored.\n     *\n     * <p>Use {@link #LIFECYCLE} for progress-indicating messages.\n     */\n    INFO,\n\n    /** Useful for debugging. */\n    DEBUG\n  }\n\n  public static LogEvent error(String message) {\n    return new LogEvent(Level.ERROR, message);\n  }\n\n  public static LogEvent lifecycle(String message) {\n    return new LogEvent(Level.LIFECYCLE, message);\n  }\n\n  public static LogEvent progress(String message) {\n    return new LogEvent(Level.PROGRESS, message);\n  }\n\n  public static LogEvent warn(String message) {\n    return new LogEvent(Level.WARN, message);\n  }\n\n  public static LogEvent info(String message) {\n    return new LogEvent(Level.INFO, message);\n  }\n\n  public static LogEvent debug(String message) {\n    return new LogEvent(Level.DEBUG, message);\n  }\n\n  private final Level level;\n  private final String message;\n\n  private LogEvent(Level level, String message) {\n    this.level = level;\n    this.message = message;\n  }\n\n  /**\n   * Gets the log level to log at.\n   *\n   * @return the log level\n   */\n  public Level getLevel() {\n    return level;\n  }\n\n  /**\n   * Gets the log message.\n   *\n   * @return the log message\n   */\n  public String getMessage() {\n    return message;\n  }\n\n  @VisibleForTesting\n  @Override\n  public boolean equals(Object other) {\n    if (other == this) {\n      return true;\n    }\n    if (!(other instanceof LogEvent)) {\n      return false;\n    }\n\n    LogEvent otherLogEvent = (LogEvent) other;\n    return level == otherLogEvent.level && message.equals(otherLogEvent.message);\n  }\n\n  @VisibleForTesting\n  @Override\n  public int hashCode() {\n    return Objects.hash(level, message);\n  }\n\n  @Override\n  public String toString() {\n    return \"LogEvent [level=\" + level + \", message=\" + message + \"]\";\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/MainClassFinder.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.common.base.Preconditions;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\n\n/**\n * Finds main classes in a list of class files. Main classes are classes that define a valid main\n * method.\n *\n * <p>For class files compiled with Java 25 or later (JEP 512), valid main methods include:\n *\n * <ul>\n *   <li>{@code static void main(String[] args)} - with public, protected, or package-private access\n *   <li>{@code static void main()} - static main without parameters\n *   <li>{@code void main(String[] args)} - instance main with parameters\n *   <li>{@code void main()} - instance main without parameters\n * </ul>\n *\n * <p>For class files compiled with earlier Java versions, only the traditional {@code public static\n * void main(String[] args)} is recognized.\n */\npublic class MainClassFinder {\n\n  /** The result of a call to {@link #find}. */\n  public static class Result {\n\n    /** The type of result. */\n    public enum Type {\n\n      // Found a single main class.\n      MAIN_CLASS_FOUND,\n\n      // Did not find any main class.\n      MAIN_CLASS_NOT_FOUND,\n\n      // Found multiple main classes.\n      MULTIPLE_MAIN_CLASSES\n    }\n\n    private static Result success(String foundMainClass) {\n      return new Result(Type.MAIN_CLASS_FOUND, Collections.singletonList(foundMainClass));\n    }\n\n    private static Result mainClassNotFound() {\n      return new Result(Type.MAIN_CLASS_NOT_FOUND, Collections.emptyList());\n    }\n\n    private static Result multipleMainClasses(List<String> foundMainClasses) {\n      return new Result(Type.MULTIPLE_MAIN_CLASSES, foundMainClasses);\n    }\n\n    private final Type type;\n    private final List<String> foundMainClasses;\n\n    private Result(Type type, List<String> foundMainClasses) {\n      this.foundMainClasses = foundMainClasses;\n      this.type = type;\n    }\n\n    /**\n     * Gets the found main class. Only call if {@link #getType} is {@link Type#MAIN_CLASS_FOUND}.\n     *\n     * @return the found main class\n     */\n    public String getFoundMainClass() {\n      Preconditions.checkState(Type.MAIN_CLASS_FOUND == type);\n      Preconditions.checkState(foundMainClasses.size() == 1);\n      return foundMainClasses.get(0);\n    }\n\n    /**\n     * Gets the type of the result.\n     *\n     * @return the type of the result\n     */\n    public Type getType() {\n      return type;\n    }\n\n    /**\n     * Gets the found main classes.\n     *\n     * @return the found main classes\n     */\n    public List<String> getFoundMainClasses() {\n      return foundMainClasses;\n    }\n  }\n\n  /** {@link ClassVisitor} that keeps track of whether or not it has visited a main class. */\n  private static class MainClassVisitor extends ClassVisitor {\n\n    /** Java 25 class file major version (flexible main methods finalized). */\n    private static final int JAVA_25_CLASS_VERSION = 69;\n\n    /** The return/argument types for main with String[] parameter. */\n    private static final String MAIN_WITH_ARGS_DESCRIPTOR =\n        org.objectweb.asm.Type.getMethodDescriptor(\n            org.objectweb.asm.Type.VOID_TYPE, org.objectweb.asm.Type.getType(String[].class));\n\n    /** The return/argument types for main without parameters. */\n    private static final String MAIN_NO_ARGS_DESCRIPTOR =\n        org.objectweb.asm.Type.getMethodDescriptor(org.objectweb.asm.Type.VOID_TYPE);\n\n    /** Optional modifiers that main may or may not have. */\n    private static final int OPTIONAL_MODIFIERS =\n        Opcodes.ACC_FINAL | Opcodes.ACC_DEPRECATED | Opcodes.ACC_VARARGS | Opcodes.ACC_SYNTHETIC;\n\n    private boolean visitedMainClass;\n    private int classVersion;\n\n    private MainClassVisitor() {\n      super(Opcodes.ASM9);\n    }\n\n    @Override\n    public void visit(\n        int version,\n        int access,\n        String name,\n        String signature,\n        String superName,\n        String[] interfaces) {\n      this.classVersion = version;\n      super.visit(version, access, name, signature, superName, interfaces);\n    }\n\n    @Override\n    @Nullable\n    public MethodVisitor visitMethod(\n        int access, String name, String descriptor, String signature, String[] exceptions) {\n      if (!name.equals(\"main\")) {\n        return null;\n      }\n\n      if ((access & Opcodes.ACC_PRIVATE) != 0) {\n        return null;\n      }\n\n      // For class files before Java 25, only traditional main is valid\n      if (classVersion < JAVA_25_CLASS_VERSION) {\n        // Traditional main: public static void main(String[] args)\n        int requiredAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;\n        if ((access & ~OPTIONAL_MODIFIERS) == requiredAccess\n            && descriptor.equals(MAIN_WITH_ARGS_DESCRIPTOR)) {\n          visitedMainClass = true;\n        }\n        return null;\n      }\n\n      // For Java 25+, check flexible main method signatures (JEP 512)\n      boolean isValidDescriptor =\n          descriptor.equals(MAIN_WITH_ARGS_DESCRIPTOR)\n              || descriptor.equals(MAIN_NO_ARGS_DESCRIPTOR);\n\n      if (!isValidDescriptor) {\n        return null;\n      }\n\n      int relevantAccess =\n          access & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | OPTIONAL_MODIFIERS);\n      if (relevantAccess == Opcodes.ACC_STATIC || relevantAccess == 0) {\n        visitedMainClass = true;\n      }\n\n      return null;\n    }\n  }\n\n  /**\n   * Tries to find classes with valid main methods (see class javadoc) in {@code files}.\n   *\n   * @param files the files to search\n   * @param logger a {@link Consumer} used to handle log events\n   * @return the {@link Result} of the main class finding attempt\n   */\n  public static Result find(List<Path> files, Consumer<LogEvent> logger) {\n    List<String> mainClasses = new ArrayList<>();\n    for (Path file : files) {\n      // Makes sure classFile is valid.\n      if (!Files.exists(file)) {\n        logger.accept(LogEvent.debug(\"MainClassFinder: \" + file + \" does not exist; ignoring\"));\n        continue;\n      }\n      if (!Files.isRegularFile(file)) {\n        logger.accept(\n            LogEvent.debug(\"MainClassFinder: \" + file + \" is not a regular file; skipping\"));\n        continue;\n      }\n      if (!file.toString().endsWith(\".class\")) {\n        logger.accept(\n            LogEvent.debug(\"MainClassFinder: \" + file + \" is not a class file; skipping\"));\n        continue;\n      }\n\n      MainClassVisitor mainClassVisitor = new MainClassVisitor();\n      try (InputStream classFileInputStream = Files.newInputStream(file)) {\n        ClassReader reader = new ClassReader(classFileInputStream);\n        reader.accept(mainClassVisitor, 0);\n        if (mainClassVisitor.visitedMainClass) {\n          mainClasses.add(reader.getClassName().replace('/', '.'));\n        }\n\n      } catch (IllegalArgumentException ex) {\n        throw new UnsupportedOperationException(\n            \"Check the full stace trace, and if the root cause is from ASM ClassReader about \"\n                + \"unsupported class file version, see \"\n                + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md\"\n                + \"#i-am-seeing-unsupported-class-file-major-version-when-building\",\n            ex);\n\n      } catch (ArrayIndexOutOfBoundsException ignored) {\n        // Not a valid class file (thrown by ClassReader if it reads an invalid format)\n        logger.accept(LogEvent.warn(\"Invalid class file found: \" + file));\n\n      } catch (IOException ignored) {\n        // Could not read class file.\n        logger.accept(LogEvent.warn(\"Could not read file: \" + file));\n      }\n    }\n\n    if (mainClasses.size() == 1) {\n      // Valid class found.\n      return Result.success(mainClasses.get(0));\n    }\n    if (mainClasses.isEmpty()) {\n      // No main class found anywhere.\n      return Result.mainClassNotFound();\n    }\n    // More than one main class found.\n    return Result.multipleMainClasses(mainClasses);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/Ports.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.common.base.Strings;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/** Utility for parsing Docker/OCI ports from text representations. */\npublic class Ports {\n\n  /**\n   * Pattern used for parsing information out of exposed port configurations.\n   *\n   * <p>Example matches: 100, 200-210, 1000/tcp, 2000/udp, 500-600/tcp\n   */\n  private static final Pattern portPattern = Pattern.compile(\"(\\\\d+)(?:-(\\\\d+))?(?:/(tcp|udp))?\");\n\n  /**\n   * Converts/validates a list of strings representing port ranges to an expanded list of {@link\n   * Port}s.\n   *\n   * <p>For example: [\"1000\", \"2000-2002\"] will expand to a list of {@link Port}s with the port\n   * numbers [1000, 2000, 2001, 2002]\n   *\n   * @param ports the list of port numbers/ranges, with an optional protocol separated by a '/'\n   *     (defaults to TCP if missing).\n   * @return the ports as a list of {@link Port}\n   * @throws NumberFormatException if any of the ports are in an invalid format or out of range\n   */\n  public static Set<Port> parse(List<String> ports) throws NumberFormatException {\n    Set<Port> result = new HashSet<>();\n\n    for (String port : ports) {\n      Matcher matcher = portPattern.matcher(port);\n\n      if (!matcher.matches()) {\n        throw new NumberFormatException(\n            \"Invalid port configuration: '\"\n                + port\n                + \"'. Make sure the port is a single number or a range of two numbers separated \"\n                + \"with a '-', with or without protocol specified (e.g. '<portNum>/tcp' or \"\n                + \"'<portNum>/udp').\");\n      }\n\n      // Parse protocol\n      int min = Integer.parseInt(matcher.group(1));\n      int max = min;\n      if (!Strings.isNullOrEmpty(matcher.group(2))) {\n        max = Integer.parseInt(matcher.group(2));\n      }\n      String protocol = matcher.group(3);\n\n      // Error if configured as 'max-min' instead of 'min-max'\n      if (min > max) {\n        throw new NumberFormatException(\n            \"Invalid port range '\" + port + \"'; smaller number must come first.\");\n      }\n\n      // Warn for possibly invalid port numbers\n      if (min < 1 || max > 65535) {\n        throw new NumberFormatException(\n            \"Port number '\" + port + \"' is out of usual range (1-65535).\");\n      }\n\n      for (int portNumber = min; portNumber <= max; portNumber++) {\n        result.add(Port.parseProtocol(portNumber, protocol));\n      }\n    }\n\n    return result;\n  }\n\n  private Ports() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryAuthenticationFailedException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.text.MessageFormat;\n\n/** Thrown because registry authentication failed. */\npublic class RegistryAuthenticationFailedException extends RegistryException {\n\n  private static final String REASON = \"Failed to authenticate with registry {0}/{1} because: {2}\";\n  private final String serverUrl;\n  private final String imageName;\n\n  /**\n   * Creates a new exception with a human readable message.\n   *\n   * @param serverUrl the registry server url\n   * @param imageName the image name that requires authentication\n   * @param cause the underlying cause that triggered this exception\n   */\n  public RegistryAuthenticationFailedException(\n      String serverUrl, String imageName, Throwable cause) {\n    super(MessageFormat.format(REASON, serverUrl, imageName, cause.getMessage()), cause);\n    this.serverUrl = serverUrl;\n    this.imageName = imageName;\n  }\n\n  /**\n   * Creates a new exception with a human readable message.\n   *\n   * @param serverUrl the registry server url\n   * @param imageName the image name that requires authentication\n   * @param reason the underlying reason that triggered this exception\n   */\n  public RegistryAuthenticationFailedException(String serverUrl, String imageName, String reason) {\n    super(MessageFormat.format(REASON, serverUrl, imageName, reason));\n    this.serverUrl = serverUrl;\n    this.imageName = imageName;\n  }\n\n  /**\n   * The server being authenticated.\n   *\n   * @return the server being authenticated\n   */\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  /**\n   * The image being authenticated.\n   *\n   * @return the image being authenticated\n   */\n  public String getImageName() {\n    return imageName;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport javax.annotation.Nullable;\n\n/** Thrown when interacting with a registry. */\npublic class RegistryException extends Exception {\n\n  public RegistryException(String message, @Nullable Throwable cause) {\n    super(message, cause);\n  }\n\n  public RegistryException(String message) {\n    super(message);\n  }\n\n  public RegistryException(Throwable cause) {\n    super(cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryImage.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\n/**\n * Defines an image on a container registry that can be used as either a source or target image.\n *\n * <p>The registry portion of the image reference determines which registry to the image lives (or\n * should live) on. The repository portion is the namespace within the registry. The tag is a label\n * to easily identify an image among all the images in the repository. See {@link ImageReference}\n * for more details.\n *\n * <p>When configuring credentials (via {@link #addCredential} for example), make sure the\n * credentials are valid push (for using this as a target image) or pull (for using this as a source\n * image) credentials for the repository specified via the image reference.\n */\npublic class RegistryImage {\n\n  /**\n   * Instantiate with the image reference to use.\n   *\n   * @param imageReference the image reference\n   * @return a new {@link RegistryImage}\n   */\n  public static RegistryImage named(ImageReference imageReference) {\n    return new RegistryImage(imageReference);\n  }\n\n  /**\n   * Instantiate with the image reference to use.\n   *\n   * @param imageReference the image reference\n   * @return a new {@link RegistryImage}\n   * @throws InvalidImageReferenceException if {@code imageReference} is not a valid image reference\n   */\n  public static RegistryImage named(String imageReference) throws InvalidImageReferenceException {\n    return named(ImageReference.parse(imageReference));\n  }\n\n  private final ImageReference imageReference;\n  private final List<CredentialRetriever> credentialRetrievers = new ArrayList<>();\n\n  /** Instantiate with {@link #named}. */\n  private RegistryImage(ImageReference imageReference) {\n    this.imageReference = imageReference;\n  }\n\n  /**\n   * Adds a username-password credential to use to push/pull the image. This is a shorthand for\n   * {@code addCredentialRetriever(() -> Optional.of(Credential.basic(username, password)))}.\n   *\n   * @param username the username\n   * @param password the password\n   * @return this\n   */\n  public RegistryImage addCredential(String username, String password) {\n    addCredentialRetriever(() -> Optional.of(Credential.from(username, password)));\n    return this;\n  }\n\n  /**\n   * Adds {@link CredentialRetriever} to fetch push/pull credentials for the image. Credential\n   * retrievers are attempted in the order in which they are specified until credentials are\n   * successfully retrieved.\n   *\n   * <p>Example usage:\n   *\n   * <pre>{@code\n   * .addCredentialRetriever(() -> {\n   *   if (!Files.exists(\"secret.txt\") {\n   *     return Optional.empty();\n   *   }\n   *   try {\n   *     String password = fetchPasswordFromFile(\"secret.txt\");\n   *     return Credential.basic(\"myaccount\", password);\n   *\n   *   } catch (IOException ex) {\n   *     throw new CredentialRetrievalException(\"Failed to load password\", ex);\n   *   }\n   * })\n   * }</pre>\n   *\n   * @param credentialRetriever the {@link CredentialRetriever} to add\n   * @return this\n   */\n  public RegistryImage addCredentialRetriever(CredentialRetriever credentialRetriever) {\n    credentialRetrievers.add(credentialRetriever);\n    return this;\n  }\n\n  ImageReference getImageReference() {\n    return imageReference;\n  }\n\n  List<CredentialRetriever> getCredentialRetrievers() {\n    return credentialRetrievers;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryUnauthorizedException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.api.client.http.HttpResponseException;\nimport com.google.cloud.tools.jib.http.ResponseException;\n\n/** Thrown when a registry request was unauthorized and therefore authentication is needed. */\npublic class RegistryUnauthorizedException extends RegistryException {\n\n  private final String registry;\n  private final String repository;\n\n  /**\n   * Identifies the image registry and repository that denied access.\n   *\n   * @param registry the image registry\n   * @param repository the image repository\n   * @param cause the cause\n   */\n  public RegistryUnauthorizedException(\n      String registry, String repository, ResponseException cause) {\n    super(\"Unauthorized for \" + registry + \"/\" + repository, cause);\n    this.registry = registry;\n    this.repository = repository;\n  }\n\n  public String getImageReference() {\n    return registry + \"/\" + repository;\n  }\n\n  public HttpResponseException getHttpResponseException() {\n    return (HttpResponseException) getCause().getCause();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/api/TarImage.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * Builds to a tarball archive.\n *\n * <p>Usage example:\n *\n * <pre>{@code\n * TarImage tarImage = TarImage.at(Paths.get(\"image.tar\"))\n *                             .named(\"myimage\");\n * }</pre>\n */\npublic class TarImage {\n\n  /**\n   * Constructs a {@link TarImage} with the specified path.\n   *\n   * @param path the path to the tarball archive\n   * @return a new {@link TarImage}\n   */\n  public static TarImage at(Path path) {\n    return new TarImage(path);\n  }\n\n  private final Path path;\n  @Nullable private ImageReference imageReference;\n\n  /** Instantiate with {@link #at}. */\n  private TarImage(Path path) {\n    this.path = path;\n  }\n\n  /**\n   * Sets the name of the image. This is the name that shows up when the tar is loaded by the Docker\n   * daemon.\n   *\n   * @param imageReference the image reference\n   * @return this\n   */\n  public TarImage named(ImageReference imageReference) {\n    this.imageReference = imageReference;\n    return this;\n  }\n\n  /**\n   * Sets the name of the image. This is the name that shows up when the tar is loaded by the Docker\n   * daemon.\n   *\n   * @param imageReference the image reference\n   * @return this\n   * @throws InvalidImageReferenceException if {@code imageReference} is not a valid image reference\n   */\n  public TarImage named(String imageReference) throws InvalidImageReferenceException {\n    return named(ImageReference.parse(imageReference));\n  }\n\n  Path getPath() {\n    return path;\n  }\n\n  Optional<ImageReference> getImageReference() {\n    return Optional.ofNullable(imageReference);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/Blob.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/** Holds a BLOB source for writing to an {@link OutputStream}. */\npublic interface Blob {\n\n  /**\n   * Writes the BLOB to an {@link OutputStream}. Does not close the {@code outputStream}.\n   *\n   * @param outputStream the {@link OutputStream} to write to\n   * @return the {@link BlobDescriptor} of the written BLOB\n   * @throws IOException if writing the BLOB fails\n   */\n  BlobDescriptor writeTo(OutputStream outputStream) throws IOException;\n\n  /**\n   * Whether {@link #writeTo(OutputStream)} is retryable.\n   *\n   * @return {@code true} if {@link #writeTo(OutputStream)} can be called multiple times. {@code\n   *     false} otherwise.\n   */\n  boolean isRetryable();\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/BlobDescriptor.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\n\n/** Contains properties describing a BLOB, including its digest and possibly its size (in bytes). */\npublic class BlobDescriptor {\n\n  private final DescriptorDigest digest;\n\n  /** The size of the BLOB (in bytes). Negative if unknown. */\n  private final long size;\n\n  public BlobDescriptor(long size, DescriptorDigest digest) {\n    this.size = size;\n    this.digest = digest;\n  }\n\n  /**\n   * Initialize with just digest.\n   *\n   * @param digest the digest to initialize the {@link BlobDescriptor} from\n   */\n  public BlobDescriptor(DescriptorDigest digest) {\n    this(-1, digest);\n  }\n\n  public boolean hasSize() {\n    return size >= 0;\n  }\n\n  public DescriptorDigest getDigest() {\n    return digest;\n  }\n\n  public long getSize() {\n    return size;\n  }\n\n  /**\n   * Checks if two {@link BlobDescriptor}s are equal.\n   *\n   * <p>Two blobs are equal if their:\n   *\n   * <ol>\n   *   <li>{@code digest}s are not null and equal, and\n   *   <li>{@code size}s are non-negative and equal\n   * </ol>\n   */\n  @Override\n  public boolean equals(Object obj) {\n    if (obj == this) {\n      return true;\n    }\n    if (size < 0 || !(obj instanceof BlobDescriptor)) {\n      return false;\n    }\n\n    BlobDescriptor other = (BlobDescriptor) obj;\n    return size == other.getSize() && digest.equals(other.getDigest());\n  }\n\n  @Override\n  public int hashCode() {\n    int result = digest.hashCode();\n    result = 31 * result + (int) (size ^ (size >>> 32));\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"digest: \" + digest + \", size: \" + size;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/Blobs.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.hash.WritableContents;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\n\n/** Static methods for {@link Blob}. */\npublic class Blobs {\n\n  public static Blob from(InputStream inputStream) {\n    return new InputStreamBlob(inputStream);\n  }\n\n  public static Blob from(Path file) {\n    return new FileBlob(file);\n  }\n\n  public static Blob from(JsonTemplate template) {\n    return new JsonBlob(template);\n  }\n\n  /**\n   * Creates a {@link StringBlob} with UTF-8 encoding.\n   *\n   * @param content the string to create the blob from\n   * @return the {@link StringBlob}\n   */\n  public static Blob from(String content) {\n    return new StringBlob(content);\n  }\n\n  public static Blob from(WritableContents writable, boolean retryable) {\n    return new WritableContentsBlob(writable, retryable);\n  }\n\n  /**\n   * Writes the BLOB to a string with UTF-8 decoding.\n   *\n   * @param blob the BLOB to write\n   * @return the BLOB contents as a string\n   * @throws IOException if writing out the BLOB contents fails\n   */\n  public static String writeToString(Blob blob) throws IOException {\n    return new String(writeToByteArray(blob), StandardCharsets.UTF_8);\n  }\n\n  /**\n   * Writes the BLOB to a byte array.\n   *\n   * @param blob the BLOB to write\n   * @return the BLOB contents as a byte array\n   * @throws IOException if writing out the BLOB contents fails\n   */\n  public static byte[] writeToByteArray(Blob blob) throws IOException {\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    blob.writeTo(byteArrayOutputStream);\n    return byteArrayOutputStream.toByteArray();\n  }\n\n  private Blobs() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/FileBlob.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.hash.Digests;\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/** A {@link Blob} that holds a {@link Path}. */\nclass FileBlob implements Blob {\n\n  private final Path file;\n\n  FileBlob(Path file) {\n    this.file = file;\n  }\n\n  @Override\n  public BlobDescriptor writeTo(OutputStream outputStream) throws IOException {\n    try (InputStream fileIn = new BufferedInputStream(Files.newInputStream(file))) {\n      return Digests.computeDigest(fileIn, outputStream);\n    }\n  }\n\n  @Override\n  public boolean isRetryable() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/InputStreamBlob.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.hash.Digests;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/** A {@link Blob} that holds an {@link InputStream}. */\nclass InputStreamBlob implements Blob {\n\n  private final InputStream inputStream;\n\n  /** Indicates if the {@link Blob} has already been written or not. */\n  private boolean isWritten = false;\n\n  InputStreamBlob(InputStream inputStream) {\n    this.inputStream = inputStream;\n  }\n\n  @Override\n  public BlobDescriptor writeTo(OutputStream outStream) throws IOException {\n    // Cannot rewrite.\n    if (isWritten) {\n      throw new IllegalStateException(\"Cannot rewrite Blob backed by an InputStream\");\n    }\n    try (InputStream inStream = this.inputStream) {\n      return Digests.computeDigest(inStream, outStream);\n\n    } finally {\n      isWritten = true;\n    }\n  }\n\n  @Override\n  public boolean isRetryable() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/JsonBlob.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/** A {@link Blob} that holds {@link JsonTemplate}. */\nclass JsonBlob implements Blob {\n\n  private final JsonTemplate template;\n\n  JsonBlob(JsonTemplate template) {\n    this.template = template;\n  }\n\n  @Override\n  public BlobDescriptor writeTo(OutputStream outputStream) throws IOException {\n    return Digests.computeDigest(template, outputStream);\n  }\n\n  @Override\n  public boolean isRetryable() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/StringBlob.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.hash.Digests;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\n\n/** A {@link Blob} that holds a {@link String}. Encodes in UTF-8 when writing in bytes. */\nclass StringBlob implements Blob {\n\n  private final String content;\n\n  StringBlob(String content) {\n    this.content = content;\n  }\n\n  @Override\n  public BlobDescriptor writeTo(OutputStream outputStream) throws IOException {\n    try (InputStream stringIn =\n        new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {\n      return Digests.computeDigest(stringIn, outputStream);\n    }\n  }\n\n  @Override\n  public boolean isRetryable() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/blob/WritableContentsBlob.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.hash.WritableContents;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/** A {@link Blob} that holds {@link WritableContents}. */\nclass WritableContentsBlob implements Blob {\n\n  private final WritableContents writableContents;\n  private final boolean retryable;\n\n  WritableContentsBlob(WritableContents writableContents, boolean retryable) {\n    this.writableContents = writableContents;\n    this.retryable = retryable;\n  }\n\n  @Override\n  public BlobDescriptor writeTo(OutputStream outputStream) throws IOException {\n    return Digests.computeDigest(writableContents, outputStream);\n  }\n\n  @Override\n  public boolean isRetryable() {\n    return retryable;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/ProgressEventDispatcher.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder;\n\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.cloud.tools.jib.event.progress.Allocation;\nimport com.google.common.base.Preconditions;\nimport java.io.Closeable;\n\n/**\n * Dispatches {@link ProgressEvent}s associated with a managed {@link Allocation}. Keeps track of\n * the allocation units that are remaining so that it can emit the remaining progress units upon\n * {@link #close}.\n *\n * <p>This class is <em>not</em> thread-safe. Only use a single instance per thread and create child\n * instances with {@link #newChildProducer}.\n */\npublic class ProgressEventDispatcher implements Closeable {\n\n  /**\n   * Creates a new {@link ProgressEventDispatcher} based off an existing {@link\n   * ProgressEventDispatcher}. {@link #create} should only be called once.\n   */\n  @FunctionalInterface\n  public interface Factory {\n\n    /**\n     * Creates the {@link ProgressEventDispatcher} with an associated {@link Allocation}.\n     *\n     * @param description user-facing description of what the allocation represents\n     * @param allocationUnits number of allocation units\n     * @return the new {@link ProgressEventDispatcher}\n     */\n    ProgressEventDispatcher create(String description, long allocationUnits);\n  }\n\n  /**\n   * Creates a new {@link ProgressEventDispatcher} with a root {@link Allocation}.\n   *\n   * @param eventHandlers the {@link EventHandlers}\n   * @param description user-facing description of what the allocation represents\n   * @param allocationUnits number of allocation units\n   * @return a new {@link ProgressEventDispatcher}\n   */\n  public static ProgressEventDispatcher newRoot(\n      EventHandlers eventHandlers, String description, long allocationUnits) {\n    return newProgressEventDispatcher(\n        eventHandlers, Allocation.newRoot(description, allocationUnits));\n  }\n\n  /**\n   * Creates a new {@link ProgressEventDispatcher} and dispatches a new {@link ProgressEvent} with\n   * progress 0 for {@code allocation}.\n   *\n   * @param eventHandlers the {@link EventHandlers}\n   * @param allocation the {@link Allocation} to manage\n   * @return a new {@link ProgressEventDispatcher}\n   */\n  private static ProgressEventDispatcher newProgressEventDispatcher(\n      EventHandlers eventHandlers, Allocation allocation) {\n    ProgressEventDispatcher progressEventDispatcher =\n        new ProgressEventDispatcher(eventHandlers, allocation);\n    progressEventDispatcher.dispatchProgress(0);\n    return progressEventDispatcher;\n  }\n\n  private final EventHandlers eventHandlers;\n  private final Allocation allocation;\n\n  private long remainingAllocationUnits;\n  private boolean closed = false;\n\n  private ProgressEventDispatcher(EventHandlers eventHandlers, Allocation allocation) {\n    this.eventHandlers = eventHandlers;\n    this.allocation = allocation;\n\n    remainingAllocationUnits = allocation.getAllocationUnits();\n  }\n\n  /**\n   * Creates a new {@link Factory} for a {@link ProgressEventDispatcher} that manages a child {@link\n   * Allocation}. Since each child {@link Allocation} accounts for 1 allocation unit of its parent,\n   * this method decrements the {@link #remainingAllocationUnits} by {@code 1}.\n   *\n   * @return a new {@link Factory}\n   */\n  public Factory newChildProducer() {\n    decrementRemainingAllocationUnits(1);\n\n    return new Factory() {\n\n      private boolean used = false;\n\n      @Override\n      public ProgressEventDispatcher create(String description, long allocationUnits) {\n        Preconditions.checkState(!used);\n        used = true;\n        return newProgressEventDispatcher(\n            eventHandlers, allocation.newChild(description, allocationUnits));\n      }\n    };\n  }\n\n  /** Emits the remaining allocation units as progress units in a {@link ProgressEvent}. */\n  @Override\n  public void close() {\n    if (remainingAllocationUnits > 0) {\n      dispatchProgress(remainingAllocationUnits);\n    }\n    closed = true;\n  }\n\n  /**\n   * Dispatches a {@link ProgressEvent} representing {@code progressUnits} of progress on the\n   * managed {@link #allocation}.\n   *\n   * @param progressUnits units of progress\n   */\n  public void dispatchProgress(long progressUnits) {\n    long unitsDecremented = decrementRemainingAllocationUnits(progressUnits);\n    eventHandlers.dispatch(new ProgressEvent(allocation, unitsDecremented));\n  }\n\n  /**\n   * Decrements remaining allocation units by {@code units} but no more than the remaining\n   * allocation units (which may be 0). Returns the actual units decremented, which never exceeds\n   * {@code units}.\n   *\n   * @param units units to decrement\n   * @return units actually decremented\n   */\n  private long decrementRemainingAllocationUnits(long units) {\n    Preconditions.checkState(!closed);\n\n    if (remainingAllocationUnits >= units) {\n      remainingAllocationUnits -= units;\n      return units;\n    }\n\n    long actualDecrement = remainingAllocationUnits;\n    remainingAllocationUnits = 0;\n    return actualDecrement;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/Timer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder;\n\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport java.time.Clock;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/** Times code execution intervals. Call {@link #lap} at the end of each interval. */\nclass Timer implements TimerEvent.Timer {\n\n  private final Clock clock;\n  @Nullable private final Timer parentTimer;\n\n  private final Instant startTime;\n  private Instant lapStartTime;\n\n  Timer(Clock clock, @Nullable Timer parentTimer) {\n    this.clock = clock;\n    this.parentTimer = parentTimer;\n\n    startTime = clock.instant();\n    lapStartTime = startTime;\n  }\n\n  @Override\n  public Optional<? extends Timer> getParent() {\n    return Optional.ofNullable(parentTimer);\n  }\n\n  /**\n   * Captures the time since last lap or creation, and resets the start time.\n   *\n   * @return the duration of the last lap, or since creation\n   */\n  Duration lap() {\n    Instant now = clock.instant();\n    Duration duration = Duration.between(lapStartTime, now);\n    lapStartTime = now;\n    return duration;\n  }\n\n  /**\n   * Gets the total elapsed time since creation.\n   *\n   * @return the total elapsed time\n   */\n  Duration getElapsedTime() {\n    return Duration.between(startTime, clock.instant());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/TimerEventDispatcher.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder;\n\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport com.google.cloud.tools.jib.event.events.TimerEvent.State;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.Closeable;\nimport java.time.Clock;\nimport java.time.Duration;\nimport javax.annotation.Nullable;\n\n/** Handles {@link Timer}s to dispatch {@link TimerEvent}s. */\npublic class TimerEventDispatcher implements Closeable {\n\n  private static final Clock DEFAULT_CLOCK = Clock.systemUTC();\n\n  private final EventHandlers eventHandlers;\n  private final String description;\n\n  private final Clock clock;\n  private final Timer timer;\n\n  /**\n   * Creates a new {@link TimerEventDispatcher}.\n   *\n   * @param eventHandlers the {@link EventHandlers} used to dispatch the {@link TimerEvent}s\n   * @param description the default description for the {@link TimerEvent}s\n   */\n  public TimerEventDispatcher(EventHandlers eventHandlers, String description) {\n    this(eventHandlers, description, DEFAULT_CLOCK, null);\n  }\n\n  @VisibleForTesting\n  TimerEventDispatcher(\n      EventHandlers eventHandlers, String description, Clock clock, @Nullable Timer parentTimer) {\n    this.eventHandlers = eventHandlers;\n    this.description = description;\n    this.clock = clock;\n    this.timer = new Timer(clock, parentTimer);\n\n    dispatchTimerEvent(State.START, Duration.ZERO, description);\n  }\n\n  /**\n   * Creates a new {@link TimerEventDispatcher} with its parent timer as this.\n   *\n   * @param description a new description\n   * @return the new {@link TimerEventDispatcher}\n   */\n  public TimerEventDispatcher subTimer(String description) {\n    return new TimerEventDispatcher(eventHandlers, description, clock, timer);\n  }\n\n  /**\n   * Captures the time since last lap or creation and dispatches an {@link State#LAP} {@link\n   * TimerEvent}.\n   *\n   * @see #lap(String)\n   */\n  public void lap() {\n    dispatchTimerEvent(State.LAP, timer.lap(), description);\n  }\n\n  /**\n   * Captures the time since last lap or creation and dispatches an {@link State#LAP} {@link\n   * TimerEvent}.\n   *\n   * @param newDescription the description to use instead of the {@link TimerEventDispatcher}'s\n   *     description\n   */\n  public void lap(String newDescription) {\n    dispatchTimerEvent(State.LAP, timer.lap(), newDescription);\n  }\n\n  /** Laps and dispatches a {@link State#FINISHED} {@link TimerEvent} upon close. */\n  @Override\n  public void close() {\n    dispatchTimerEvent(State.FINISHED, timer.lap(), description);\n  }\n\n  private void dispatchTimerEvent(State state, Duration duration, String eventDescription) {\n    eventHandlers.dispatch(\n        new TimerEvent(state, timer, duration, timer.getElapsedTime(), eventDescription));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/AuthenticatePushStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport java.io.IOException;\nimport java.util.concurrent.Callable;\n\n/**\n * Authenticates push to a target registry using Docker Token Authentication.\n *\n * @see <a\n *     href=\"https://docs.docker.com/registry/spec/auth/token/\">https://docs.docker.com/registry/spec/auth/token/</a>\n */\nclass AuthenticatePushStep implements Callable<RegistryClient> {\n\n  @SuppressWarnings(\"InlineFormatString\")\n  private static final String DESCRIPTION = \"Authenticating push to %s\";\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  AuthenticatePushStep(\n      BuildContext buildContext, ProgressEventDispatcher.Factory progressEventDispatcherFactory) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n  }\n\n  @Override\n  public RegistryClient call() throws CredentialRetrievalException, IOException, RegistryException {\n    String registry = buildContext.getTargetImageConfiguration().getImageRegistry();\n    try (ProgressEventDispatcher progressDispatcher =\n            progressEventDispatcherFactory.create(\"authenticating push to \" + registry, 2);\n        TimerEventDispatcher ignored2 =\n            new TimerEventDispatcher(\n                buildContext.getEventHandlers(), String.format(DESCRIPTION, registry))) {\n      Credential credential =\n          RegistryCredentialRetriever.getTargetImageCredential(buildContext).orElse(null);\n      progressDispatcher.dispatchProgress(1);\n\n      RegistryClient registryClient =\n          buildContext\n              .newTargetImageRegistryClientFactory()\n              .setCredential(credential)\n              .newRegistryClient();\n      if (!registryClient.doPushBearerAuth()) {\n        // server returned \"WWW-Authenticate: Basic ...\" (e.g., local Docker registry)\n        if (credential != null && !credential.isOAuth2RefreshToken()) {\n          registryClient.configureBasicAuth();\n        }\n      }\n      return registryClient;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildAndCacheApplicationLayerStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.cache.CachedLayer;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.image.ReproducibleLayerBuilder;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\n\n/** Builds and caches application layers. */\nclass BuildAndCacheApplicationLayerStep implements Callable<PreparedLayer> {\n\n  @SuppressWarnings(\"InlineFormatString\")\n  private static final String DESCRIPTION = \"Building %s layer\";\n\n  /**\n   * Makes a list of {@link BuildAndCacheApplicationLayerStep} for dependencies, resources, and\n   * classes layers. Optionally adds an extra layer if configured to do so.\n   */\n  static ImmutableList<BuildAndCacheApplicationLayerStep> makeList(\n      BuildContext buildContext, ProgressEventDispatcher.Factory progressEventDispatcherFactory) {\n    List<FileEntriesLayer> layerConfigurations = buildContext.getLayerConfigurations();\n\n    try (ProgressEventDispatcher progressEventDispatcher =\n            progressEventDispatcherFactory.create(\n                \"launching application layer builders\", layerConfigurations.size());\n        TimerEventDispatcher ignored =\n            new TimerEventDispatcher(\n                buildContext.getEventHandlers(), \"Preparing application layer builders\")) {\n      return layerConfigurations.stream()\n          // Skips the layer if empty.\n          .filter(layerConfiguration -> !layerConfiguration.getEntries().isEmpty())\n          .map(\n              layerConfiguration ->\n                  new BuildAndCacheApplicationLayerStep(\n                      buildContext,\n                      progressEventDispatcher.newChildProducer(),\n                      layerConfiguration.getName(),\n                      layerConfiguration))\n          .collect(ImmutableList.toImmutableList());\n    }\n  }\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final String layerName;\n  private final FileEntriesLayer layerConfiguration;\n\n  private BuildAndCacheApplicationLayerStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      String layerName,\n      FileEntriesLayer layerConfiguration) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.layerName = layerName;\n    this.layerConfiguration = layerConfiguration;\n  }\n\n  @Override\n  public PreparedLayer call() throws IOException, CacheCorruptedException {\n    String description = String.format(DESCRIPTION, layerName);\n\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    eventHandlers.dispatch(LogEvent.progress(description + \"...\"));\n\n    try (ProgressEventDispatcher ignored =\n            progressEventDispatcherFactory.create(\"building \" + layerName + \" layer\", 1);\n        TimerEventDispatcher ignored2 = new TimerEventDispatcher(eventHandlers, description)) {\n      Cache cache = buildContext.getApplicationLayersCache();\n\n      ImmutableList<FileEntry> layerEntries = ImmutableList.copyOf(layerConfiguration.getEntries());\n      // Don't build the layer if it exists already.\n      Optional<CachedLayer> optionalCachedLayer = cache.retrieve(layerEntries);\n      if (optionalCachedLayer.isPresent()) {\n        return new PreparedLayer.Builder(optionalCachedLayer.get()).setName(layerName).build();\n      }\n\n      Blob layerBlob = new ReproducibleLayerBuilder(layerEntries).build();\n      CachedLayer cachedLayer = cache.writeUncompressedLayer(layerBlob, layerEntries);\n\n      eventHandlers.dispatch(LogEvent.debug(description + \" built \" + cachedLayer.getDigest()));\n\n      return new PreparedLayer.Builder(cachedLayer).setName(layerName).build();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildImageStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.cloud.tools.jib.image.json.HistoryEntry;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.UnmodifiableIterator;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport javax.annotation.Nullable;\n\n/** Builds a model {@link Image}. */\nclass BuildImageStep implements Callable<Image> {\n\n  private static final String DESCRIPTION = \"Building container configuration\";\n\n  @VisibleForTesting\n  static String truncateLongClasspath(ImmutableList<String> imageEntrypoint) {\n    List<String> truncated = new ArrayList<>();\n    UnmodifiableIterator<String> iterator = imageEntrypoint.iterator();\n    while (iterator.hasNext()) {\n      String element = iterator.next();\n      truncated.add(element);\n\n      if (element.equals(\"-cp\") || element.equals(\"-classpath\")) {\n        String classpath = iterator.next();\n        if (classpath.length() > 200) {\n          truncated.add(classpath.substring(0, 200) + \"<... classpath truncated ...>\");\n        } else {\n          truncated.add(classpath);\n        }\n      }\n    }\n    return truncated.toString();\n  }\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n  private final Image baseImage;\n  private final List<PreparedLayer> baseImageLayers;\n  private final List<PreparedLayer> applicationLayers;\n\n  BuildImageStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      Image baseImage,\n      List<PreparedLayer> baseImageLayers,\n      List<PreparedLayer> applicationLayers) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.baseImage = baseImage;\n    this.baseImageLayers = baseImageLayers;\n    this.applicationLayers = applicationLayers;\n  }\n\n  @Override\n  public Image call() throws LayerPropertyNotFoundException {\n    try (ProgressEventDispatcher ignored =\n            progressEventDispatcherFactory.create(\"building image format\", 1);\n        TimerEventDispatcher ignored2 =\n            new TimerEventDispatcher(buildContext.getEventHandlers(), DESCRIPTION)) {\n      // Constructs the image.\n      Image.Builder imageBuilder = Image.builder(buildContext.getTargetFormat());\n\n      // Base image layers\n      baseImageLayers.forEach(imageBuilder::addLayer);\n\n      // Passthrough config and count non-empty history entries\n      int nonEmptyLayerCount = 0;\n      for (HistoryEntry historyObject : baseImage.getHistory()) {\n        imageBuilder.addHistory(historyObject);\n        if (!historyObject.hasCorrespondingLayer()) {\n          nonEmptyLayerCount++;\n        }\n      }\n      imageBuilder\n          .setArchitecture(baseImage.getArchitecture())\n          .setOs(baseImage.getOs())\n          .addEnvironment(baseImage.getEnvironment())\n          .addLabels(baseImage.getLabels())\n          .setHealthCheck(baseImage.getHealthCheck())\n          .addExposedPorts(baseImage.getExposedPorts())\n          .addVolumes(baseImage.getVolumes())\n          .setUser(baseImage.getUser())\n          .setWorkingDirectory(baseImage.getWorkingDirectory());\n\n      ContainerConfiguration containerConfiguration = buildContext.getContainerConfiguration();\n      // Add history elements for non-empty layers that don't have one yet\n      Instant layerCreationTime = containerConfiguration.getCreationTime();\n      for (int count = 0; count < baseImageLayers.size() - nonEmptyLayerCount; count++) {\n        imageBuilder.addHistory(\n            HistoryEntry.builder()\n                .setCreationTimestamp(layerCreationTime)\n                .setComment(\"auto-generated by Jib\")\n                .build());\n      }\n\n      // Add built layers/configuration\n      for (PreparedLayer applicationLayer : applicationLayers) {\n        imageBuilder\n            .addLayer(applicationLayer)\n            .addHistory(\n                HistoryEntry.builder()\n                    .setCreationTimestamp(layerCreationTime)\n                    .setAuthor(\"Jib\")\n                    .setCreatedBy(buildContext.getToolName() + \":\" + buildContext.getToolVersion())\n                    .setComment(applicationLayer.getName())\n                    .build());\n      }\n\n      imageBuilder\n          .addEnvironment(containerConfiguration.getEnvironmentMap())\n          .setCreated(containerConfiguration.getCreationTime())\n          .setEntrypoint(computeEntrypoint(baseImage, containerConfiguration))\n          .setProgramArguments(computeProgramArguments(baseImage, containerConfiguration))\n          .addExposedPorts(containerConfiguration.getExposedPorts())\n          .addVolumes(containerConfiguration.getVolumes())\n          .addLabels(containerConfiguration.getLabels());\n      if (containerConfiguration.getUser() != null) {\n        imageBuilder.setUser(containerConfiguration.getUser());\n      }\n      if (containerConfiguration.getWorkingDirectory() != null) {\n        imageBuilder.setWorkingDirectory(containerConfiguration.getWorkingDirectory().toString());\n      }\n\n      // Gets the container configuration content descriptor.\n      return imageBuilder.build();\n    }\n  }\n\n  /**\n   * Computes the image entrypoint. If {@link ContainerConfiguration#getEntrypoint()} is null, the\n   * entrypoint is inherited from the base image. Otherwise {@link\n   * ContainerConfiguration#getEntrypoint()} is returned.\n   *\n   * @param baseImage the base image\n   * @param containerConfiguration the container configuration\n   * @return the container entrypoint\n   */\n  @Nullable\n  private ImmutableList<String> computeEntrypoint(\n      Image baseImage, ContainerConfiguration containerConfiguration) {\n    boolean shouldInherit =\n        baseImage.getEntrypoint() != null && containerConfiguration.getEntrypoint() == null;\n\n    ImmutableList<String> entrypointToUse =\n        shouldInherit ? baseImage.getEntrypoint() : containerConfiguration.getEntrypoint();\n\n    if (entrypointToUse != null) {\n      buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(\"\"));\n      if (shouldInherit) {\n        String message =\n            \"Container entrypoint set to \" + entrypointToUse + \" (inherited from base image)\";\n        buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(message));\n      } else {\n        String message = \"Container entrypoint set to \" + truncateLongClasspath(entrypointToUse);\n        buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(message));\n      }\n    }\n\n    return entrypointToUse;\n  }\n\n  /**\n   * Computes the image program arguments. If {@link ContainerConfiguration#getEntrypoint()} and\n   * {@link ContainerConfiguration#getProgramArguments()} are null, the program arguments are\n   * inherited from the base image. Otherwise {@link ContainerConfiguration#getProgramArguments()}\n   * is returned.\n   *\n   * @param baseImage the base image\n   * @param containerConfiguration the container configuration\n   * @return the container program arguments\n   */\n  @Nullable\n  private ImmutableList<String> computeProgramArguments(\n      Image baseImage, ContainerConfiguration containerConfiguration) {\n    boolean shouldInherit =\n        baseImage.getProgramArguments() != null\n            // Inherit CMD only when inheriting ENTRYPOINT.\n            && containerConfiguration.getEntrypoint() == null\n            && containerConfiguration.getProgramArguments() == null;\n\n    ImmutableList<String> programArgumentsToUse =\n        shouldInherit\n            ? baseImage.getProgramArguments()\n            : containerConfiguration.getProgramArguments();\n\n    if (programArgumentsToUse != null) {\n      String logSuffix = shouldInherit ? \" (inherited from base image)\" : \"\";\n      String message = \"Container program arguments set to \" + programArgumentsToUse + logSuffix;\n      buildContext.getEventHandlers().dispatch(LogEvent.lifecycle(message));\n    }\n\n    return programArgumentsToUse;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStep.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.api.client.util.Preconditions;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.ImageToJsonTranslator;\nimport com.google.cloud.tools.jib.image.json.ManifestListGenerator;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.Callable;\n\n/** Builds a manifest list or a single manifest. */\nclass BuildManifestListOrSingleManifestStep implements Callable<ManifestTemplate> {\n\n  private static final String DESCRIPTION = \"Building a manifest list or a single manifest\";\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n  private final List<Image> builtImages;\n\n  BuildManifestListOrSingleManifestStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      List<Image> builtImages) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.builtImages = builtImages;\n  }\n\n  @Override\n  public ManifestTemplate call() throws IOException {\n    Preconditions.checkState(!builtImages.isEmpty(), \"no images given\");\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (TimerEventDispatcher ignored = new TimerEventDispatcher(eventHandlers, DESCRIPTION);\n        ProgressEventDispatcher ignored2 =\n            progressEventDispatcherFactory.create(\n                \"building a manifest list or a single manifest\", 1)) {\n\n      if (builtImages.size() == 1) {\n        eventHandlers.dispatch(LogEvent.info(\"Building a single manifest\"));\n        ImageToJsonTranslator imageTranslator = new ImageToJsonTranslator(builtImages.get(0));\n        BlobDescriptor configDescriptor =\n            Digests.computeDigest(imageTranslator.getContainerConfiguration());\n        return imageTranslator.getManifestTemplate(\n            buildContext.getTargetFormat(), configDescriptor);\n      }\n\n      eventHandlers.dispatch(LogEvent.info(\"Building a manifest list\"));\n      return new ManifestListGenerator(builtImages)\n          .getManifestListTemplate(buildContext.getTargetFormat());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildResult.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageToJsonTranslator;\nimport java.io.IOException;\nimport java.util.Objects;\n\n/** Used to record the results of a build. */\npublic class BuildResult {\n\n  /**\n   * Gets a {@link BuildResult} from an {@link Image}.\n   *\n   * @param image the image\n   * @param targetFormat the target format of the image\n   * @return a new {@link BuildResult} with the image's digest and id\n   * @throws IOException if writing the digest or container configuration fails\n   */\n  static BuildResult fromImage(Image image, Class<? extends BuildableManifestTemplate> targetFormat)\n      throws IOException {\n    ImageToJsonTranslator imageToJsonTranslator = new ImageToJsonTranslator(image);\n    BlobDescriptor containerConfigurationBlobDescriptor =\n        Digests.computeDigest(imageToJsonTranslator.getContainerConfiguration());\n    BuildableManifestTemplate manifestTemplate =\n        imageToJsonTranslator.getManifestTemplate(\n            targetFormat, containerConfigurationBlobDescriptor);\n    DescriptorDigest imageDigest = Digests.computeJsonDigest(manifestTemplate);\n    DescriptorDigest imageId = containerConfigurationBlobDescriptor.getDigest();\n    return new BuildResult(imageDigest, imageId, false);\n  }\n\n  private final DescriptorDigest imageDigest;\n  private final DescriptorDigest imageId;\n  private final Boolean imagePushed;\n\n  BuildResult(DescriptorDigest imageDigest, DescriptorDigest imageId, boolean imagePushed) {\n    this.imageDigest = imageDigest;\n    this.imageId = imageId;\n    this.imagePushed = imagePushed;\n  }\n\n  public DescriptorDigest getImageDigest() {\n    return imageDigest;\n  }\n\n  public DescriptorDigest getImageId() {\n    return imageId;\n  }\n\n  public boolean isImagePushed() {\n    return imagePushed;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(imageDigest, imageId);\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof BuildResult)) {\n      return false;\n    }\n    BuildResult otherBuildResult = (BuildResult) other;\n    return imageDigest.equals(otherBuildResult.imageDigest)\n        && imageId.equals(otherBuildResult.imageId)\n        && imagePushed.equals(otherBuildResult.imagePushed);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/CheckManifestStep.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.ManifestAndDigest;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport java.io.IOException;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\n\n/** Checks the existence of a manifest. */\nclass CheckManifestStep implements Callable<Optional<ManifestAndDigest<ManifestTemplate>>> {\n\n  private static final String DESCRIPTION = \"Checking existence of manifest\";\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n  private final RegistryClient registryClient;\n  private final ManifestTemplate manifestTemplate;\n\n  CheckManifestStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      ManifestTemplate manifestTemplate) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.registryClient = registryClient;\n    this.manifestTemplate = manifestTemplate;\n  }\n\n  @Override\n  public Optional<ManifestAndDigest<ManifestTemplate>> call()\n      throws IOException, RegistryException {\n    DescriptorDigest manifestDigest = Digests.computeJsonDigest(manifestTemplate);\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (TimerEventDispatcher ignored = new TimerEventDispatcher(eventHandlers, DESCRIPTION);\n        ProgressEventDispatcher ignored2 =\n            progressEventDispatcherFactory.create(\n                \"checking existence of manifest for \" + manifestDigest, 1)) {\n      eventHandlers.dispatch(\n          LogEvent.info(\"Checking existence of manifest for \" + manifestDigest + \"...\"));\n\n      if (!JibSystemProperties.skipExistingImages()) {\n        eventHandlers.dispatch(\n            LogEvent.info(\"Skipping manifest existence check; system property set to false\"));\n        return Optional.empty();\n      }\n\n      return registryClient.checkManifest(manifestDigest.toString());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/LoadDockerStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.progress.ThrottledAccumulatingConsumer;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport java.io.IOException;\nimport java.util.concurrent.Callable;\n\n/** Adds image layers to a tarball and loads into Docker daemon. */\nclass LoadDockerStep implements Callable<BuildResult> {\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final DockerClient dockerClient;\n  private final Image builtImage;\n\n  LoadDockerStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      DockerClient dockerClient,\n      Image builtImage) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.dockerClient = dockerClient;\n    this.builtImage = builtImage;\n  }\n\n  @Override\n  public BuildResult call() throws InterruptedException, IOException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (TimerEventDispatcher ignored =\n        new TimerEventDispatcher(eventHandlers, \"Loading to Docker daemon\")) {\n      eventHandlers.dispatch(LogEvent.progress(\"Loading to Docker daemon...\"));\n      ImageTarball imageTarball =\n          new ImageTarball(\n              builtImage,\n              buildContext.getTargetImageConfiguration().getImage(),\n              buildContext.getAllTargetImageTags());\n\n      // Note: The progress reported here is not entirely accurate. The total allocation units is\n      // the size of the layers, but the progress being reported includes the config and manifest\n      // as well, so we will always go over the total progress allocation here.\n      // See https://github.com/GoogleContainerTools/jib/pull/1960#discussion_r321898390\n      try (ProgressEventDispatcher progressEventDispatcher =\n              progressEventDispatcherFactory.create(\n                  \"loading to Docker daemon\", imageTarball.getTotalLayerSize());\n          ThrottledAccumulatingConsumer throttledProgressReporter =\n              new ThrottledAccumulatingConsumer(progressEventDispatcher::dispatchProgress)) {\n        // Load the image to docker daemon.\n        eventHandlers.dispatch(\n            LogEvent.debug(dockerClient.load(imageTarball, throttledProgressReporter)));\n\n        return BuildResult.fromImage(builtImage, buildContext.getTargetFormat());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageSteps.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.fasterxml.jackson.databind.MapperFeature;\nimport com.fasterxml.jackson.databind.json.JsonMapper;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.ImageDetails;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.cache.CachedLayer;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.docker.json.DockerManifestEntryTemplate;\nimport com.google.cloud.tools.jib.event.progress.ThrottledAccumulatingConsumer;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.NotifyingOutputStream;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.LayerCountMismatchException;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.JsonToImageTranslator;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.tar.TarExtractor;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\n\n/** Extracts a tar file base image. */\npublic class LocalBaseImageSteps {\n\n  /** Contains an {@link Image} and its layers. * */\n  static class LocalImage {\n    final List<Future<PreparedLayer>> layers;\n    final ContainerConfigurationTemplate configurationTemplate;\n\n    LocalImage(\n        List<Future<PreparedLayer>> layers, ContainerConfigurationTemplate configurationTemplate) {\n      this.layers = layers;\n      this.configurationTemplate = configurationTemplate;\n    }\n  }\n\n  private LocalBaseImageSteps() {}\n\n  /**\n   * Checks the first two bytes of a file to see if it has been gzipped.\n   *\n   * @param path the file to check\n   * @return {@code true} if the file is gzipped, {@code false} if not\n   * @throws IOException if reading the file fails\n   * @see <a href=\"http://www.zlib.org/rfc-gzip.html#file-format\">GZIP file format</a>\n   */\n  @VisibleForTesting\n  static boolean isGzipped(Path path) throws IOException {\n    try (InputStream inputStream = Files.newInputStream(path)) {\n      inputStream.mark(2);\n      int magic = (inputStream.read() & 0xff) | ((inputStream.read() << 8) & 0xff00);\n      return magic == GZIPInputStream.GZIP_MAGIC;\n    }\n  }\n\n  static Callable<LocalImage> retrieveDockerDaemonLayersStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      DockerClient dockerClient,\n      TempDirectoryProvider tempDirectoryProvider) {\n    return () -> {\n      ImageReference imageReference = buildContext.getBaseImageConfiguration().getImage();\n      try (ProgressEventDispatcher progressEventDispatcher =\n              progressEventDispatcherFactory.create(\"processing base image \" + imageReference, 2);\n          TimerEventDispatcher ignored =\n              new TimerEventDispatcher(\n                  buildContext.getEventHandlers(),\n                  \"Saving \" + imageReference + \" from Docker daemon\")) {\n        ImageDetails dockerImageDetails = dockerClient.inspect(imageReference);\n        Optional<LocalImage> cachedImage =\n            getCachedDockerImage(buildContext.getBaseImageLayersCache(), dockerImageDetails);\n        if (cachedImage.isPresent()) {\n          PlatformChecker.checkManifestPlatform(\n              buildContext, cachedImage.get().configurationTemplate);\n          return cachedImage.get();\n        }\n\n        Path tarPath = tempDirectoryProvider.newDirectory().resolve(\"out.tar\");\n        long size = dockerImageDetails.getSize();\n        try (ProgressEventDispatcher dockerProgress =\n                progressEventDispatcher\n                    .newChildProducer()\n                    .create(\"saving base image \" + imageReference, size);\n            ThrottledAccumulatingConsumer throttledProgressReporter =\n                new ThrottledAccumulatingConsumer(dockerProgress::dispatchProgress)) {\n          dockerClient.save(imageReference, tarPath, throttledProgressReporter);\n        }\n\n        LocalImage localImage =\n            cacheDockerImageTar(\n                buildContext,\n                tarPath,\n                progressEventDispatcher.newChildProducer(),\n                tempDirectoryProvider);\n        PlatformChecker.checkManifestPlatform(buildContext, localImage.configurationTemplate);\n        return localImage;\n      }\n    };\n  }\n\n  static Callable<LocalImage> retrieveTarLayersStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      Path tarPath,\n      TempDirectoryProvider tempDirectoryProvider) {\n    return () -> {\n      LocalImage localImage =\n          cacheDockerImageTar(\n              buildContext, tarPath, progressEventDispatcherFactory, tempDirectoryProvider);\n      PlatformChecker.checkManifestPlatform(buildContext, localImage.configurationTemplate);\n      return localImage;\n    };\n  }\n\n  static Callable<ImagesAndRegistryClient> returnImageAndRegistryClientStep(\n      List<PreparedLayer> layers, ContainerConfigurationTemplate configurationTemplate) {\n    return () -> {\n      // Collect compressed layers and add to manifest\n      V22ManifestTemplate v22Manifest = new V22ManifestTemplate();\n      for (PreparedLayer layer : layers) {\n        BlobDescriptor descriptor = layer.getBlobDescriptor();\n        v22Manifest.addLayer(descriptor.getSize(), descriptor.getDigest());\n      }\n\n      BlobDescriptor configDescriptor = Digests.computeDigest(configurationTemplate);\n      v22Manifest.setContainerConfiguration(\n          configDescriptor.getSize(), configDescriptor.getDigest());\n      return new ImagesAndRegistryClient(\n          Collections.singletonList(\n              JsonToImageTranslator.toImage(v22Manifest, configurationTemplate)),\n          null);\n    };\n  }\n\n  @VisibleForTesting\n  static Optional<LocalImage> getCachedDockerImage(Cache cache, ImageDetails dockerImageDetails)\n      throws DigestException, IOException, CacheCorruptedException {\n    // Get config\n    Optional<ContainerConfigurationTemplate> cachedConfig =\n        cache.retrieveLocalConfig(dockerImageDetails.getImageId());\n    if (!cachedConfig.isPresent()) {\n      return Optional.empty();\n    }\n\n    // Get layers\n    List<Future<PreparedLayer>> cachedLayers = new ArrayList<>();\n    for (DescriptorDigest diffId : dockerImageDetails.getDiffIds()) {\n      Optional<CachedLayer> cachedLayer = cache.retrieveTarLayer(diffId);\n      if (!cachedLayer.isPresent()) {\n        return Optional.empty();\n      }\n      CachedLayer layer = cachedLayer.get();\n      cachedLayers.add(Futures.immediateFuture(new PreparedLayer.Builder(layer).build()));\n    }\n\n    return Optional.of(new LocalImage(cachedLayers, cachedConfig.get()));\n  }\n\n  @VisibleForTesting\n  static LocalImage cacheDockerImageTar(\n      BuildContext buildContext,\n      Path tarPath,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      TempDirectoryProvider tempDirectoryProvider)\n      throws IOException, LayerCountMismatchException {\n    ExecutorService executorService = buildContext.getExecutorService();\n    Path destination = tempDirectoryProvider.newDirectory();\n\n    try (TimerEventDispatcher ignored =\n        new TimerEventDispatcher(\n            buildContext.getEventHandlers(),\n            \"Extracting tar \" + tarPath + \" into \" + destination)) {\n      TarExtractor.extract(tarPath, destination);\n\n      DockerManifestEntryTemplate loadManifest;\n      try (InputStream manifestStream =\n          Files.newInputStream(destination.resolve(\"manifest.json\"))) {\n        loadManifest =\n            JsonMapper.builder()\n                .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)\n                .build()\n                .readValue(manifestStream, DockerManifestEntryTemplate[].class)[0];\n      }\n\n      Path configPath = destination.resolve(loadManifest.getConfig());\n      ContainerConfigurationTemplate configurationTemplate =\n          JsonTemplateMapper.readJsonFromFile(configPath, ContainerConfigurationTemplate.class);\n      // Don't compute the digest of the loaded Java JSON instance.\n      BlobDescriptor originalConfigDescriptor =\n          Blobs.from(configPath).writeTo(ByteStreams.nullOutputStream());\n\n      List<String> layerFiles = loadManifest.getLayerFiles();\n      if (configurationTemplate.getLayerCount() != layerFiles.size()) {\n        throw new LayerCountMismatchException(\n            \"Invalid base image format: manifest contains \"\n                + layerFiles.size()\n                + \" layers, but container configuration contains \"\n                + configurationTemplate.getLayerCount()\n                + \" layers\");\n      }\n      buildContext\n          .getBaseImageLayersCache()\n          .writeLocalConfig(originalConfigDescriptor.getDigest(), configurationTemplate);\n\n      // Check the first layer to see if the layers are compressed already. 'docker save' output\n      // is uncompressed, but a jib-built tar has compressed layers.\n      boolean layersAreCompressed =\n          !layerFiles.isEmpty() && isGzipped(destination.resolve(layerFiles.get(0)));\n\n      // Process layer blobs\n      try (ProgressEventDispatcher progressEventDispatcher =\n          progressEventDispatcherFactory.create(\n              \"processing base image layers\", layerFiles.size())) {\n        // Start compressing layers in parallel\n        List<Future<PreparedLayer>> preparedLayers = new ArrayList<>();\n        for (int index = 0; index < layerFiles.size(); index++) {\n          Path layerFile = destination.resolve(layerFiles.get(index));\n          DescriptorDigest diffId = configurationTemplate.getLayerDiffId(index);\n          ProgressEventDispatcher.Factory layerProgressDispatcherFactory =\n              progressEventDispatcher.newChildProducer();\n          preparedLayers.add(\n              executorService.submit(\n                  () ->\n                      compressAndCacheTarLayer(\n                          buildContext.getBaseImageLayersCache(),\n                          diffId,\n                          layerFile,\n                          layersAreCompressed,\n                          layerProgressDispatcherFactory)));\n        }\n        return new LocalImage(preparedLayers, configurationTemplate);\n      }\n    }\n  }\n\n  private static PreparedLayer compressAndCacheTarLayer(\n      Cache cache,\n      DescriptorDigest diffId,\n      Path layerFile,\n      boolean layersAreCompressed,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory)\n      throws IOException, CacheCorruptedException {\n    try (ProgressEventDispatcher childDispatcher =\n            progressEventDispatcherFactory.create(\n                \"compressing layer \" + diffId, Files.size(layerFile));\n        ThrottledAccumulatingConsumer throttledProgressReporter =\n            new ThrottledAccumulatingConsumer(childDispatcher::dispatchProgress)) {\n      // Retrieve pre-compressed layer from cache\n      Optional<CachedLayer> optionalLayer = cache.retrieveTarLayer(diffId);\n      if (optionalLayer.isPresent()) {\n        return new PreparedLayer.Builder(optionalLayer.get()).build();\n      }\n\n      // Just write layers that are already compressed\n      if (layersAreCompressed) {\n        return new PreparedLayer.Builder(cache.writeTarLayer(diffId, Blobs.from(layerFile)))\n            .build();\n      }\n\n      // Compress uncompressed layers while writing\n      Blob compressedBlob =\n          Blobs.from(\n              outputStream -> {\n                try (GZIPOutputStream compressorStream = new GZIPOutputStream(outputStream);\n                    NotifyingOutputStream notifyingOutputStream =\n                        new NotifyingOutputStream(compressorStream, throttledProgressReporter)) {\n                  Blobs.from(layerFile).writeTo(notifyingOutputStream);\n                }\n              },\n              true);\n      return new PreparedLayer.Builder(cache.writeTarLayer(diffId, compressedBlob)).build();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/ObtainBaseImageLayerStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PreparedLayer.StateInTarget;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.cache.CachedLayer;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.base.Verify;\nimport java.io.IOException;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport javax.annotation.Nullable;\n\n/** Pulls and caches a single base image layer. */\nclass ObtainBaseImageLayerStep implements Callable<PreparedLayer> {\n\n  @SuppressWarnings(\"InlineFormatString\")\n  private static final String DESCRIPTION = \"Pulling base image layer %s\";\n\n  @FunctionalInterface\n  private interface BlobExistenceChecker {\n\n    StateInTarget check(DescriptorDigest digest) throws IOException, RegistryException;\n  }\n\n  static ObtainBaseImageLayerStep forForcedDownload(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      Layer layer,\n      @Nullable RegistryClient sourceRegistryClient) {\n    BlobExistenceChecker noOpChecker = ignored -> StateInTarget.UNKNOWN;\n    return new ObtainBaseImageLayerStep(\n        buildContext, progressEventDispatcherFactory, layer, sourceRegistryClient, noOpChecker);\n  }\n\n  static ObtainBaseImageLayerStep forSelectiveDownload(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      Layer layer,\n      @Nullable RegistryClient sourceRegistryClient,\n      RegistryClient targetRegistryClient) {\n    Verify.verify(!buildContext.isOffline());\n\n    // TODO: also check if cross-repo blob mount is possible.\n    BlobExistenceChecker blobExistenceChecker =\n        digest ->\n            targetRegistryClient.checkBlob(digest).isPresent()\n                ? StateInTarget.EXISTING\n                : StateInTarget.MISSING;\n\n    return new ObtainBaseImageLayerStep(\n        buildContext,\n        progressEventDispatcherFactory,\n        layer,\n        sourceRegistryClient,\n        blobExistenceChecker);\n  }\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final Layer layer;\n  private final @Nullable RegistryClient registryClient;\n  private final BlobExistenceChecker blobExistenceChecker;\n\n  private ObtainBaseImageLayerStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      Layer layer,\n      @Nullable RegistryClient registryClient,\n      BlobExistenceChecker blobExistenceChecker) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.layer = layer;\n    this.registryClient = registryClient;\n    this.blobExistenceChecker = blobExistenceChecker;\n  }\n\n  @Override\n  public PreparedLayer call() throws IOException, CacheCorruptedException, RegistryException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    DescriptorDigest layerDigest = layer.getBlobDescriptor().getDigest();\n    try (ProgressEventDispatcher progressEventDispatcher =\n            progressEventDispatcherFactory.create(\"checking base image layer \" + layerDigest, 1);\n        TimerEventDispatcher ignored =\n            new TimerEventDispatcher(eventHandlers, String.format(DESCRIPTION, layerDigest))) {\n\n      StateInTarget stateInTarget = blobExistenceChecker.check(layerDigest);\n      if (stateInTarget == StateInTarget.EXISTING) {\n        eventHandlers.dispatch(\n            LogEvent.info(\n                \"Skipping pull; BLOB already exists on target registry : \"\n                    + layer.getBlobDescriptor()));\n        return new PreparedLayer.Builder(layer).setStateInTarget(stateInTarget).build();\n      }\n\n      Cache cache = buildContext.getBaseImageLayersCache();\n\n      // Checks if the layer already exists in the cache.\n      Optional<CachedLayer> optionalCachedLayer = cache.retrieve(layerDigest);\n      if (optionalCachedLayer.isPresent()) {\n        CachedLayer cachedLayer = optionalCachedLayer.get();\n        return new PreparedLayer.Builder(cachedLayer).setStateInTarget(stateInTarget).build();\n      } else if (buildContext.isOffline()) {\n        throw new IOException(\n            \"Cannot run Jib in offline mode; local Jib cache for base image is missing image layer \"\n                + layerDigest\n                + \". Rerun Jib in online mode with \\\"-Djib.alwaysCacheBaseImage=true\\\" to \"\n                + \"re-download the base image layers.\");\n      }\n\n      try (ThrottledProgressEventDispatcherWrapper progressEventDispatcherWrapper =\n          new ThrottledProgressEventDispatcherWrapper(\n              progressEventDispatcher.newChildProducer(),\n              \"pulling base image layer \" + layerDigest)) {\n        CachedLayer cachedLayer =\n            cache.writeCompressedLayer(\n                Verify.verifyNotNull(registryClient)\n                    .pullBlob(\n                        layerDigest,\n                        progressEventDispatcherWrapper::setProgressTarget,\n                        progressEventDispatcherWrapper::dispatchProgress));\n        return new PreparedLayer.Builder(cachedLayer).setStateInTarget(stateInTarget).build();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException;\nimport com.google.common.base.Verify;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.Set;\n\n/** Provides helper methods to check platforms. */\npublic class PlatformChecker {\n\n  private PlatformChecker() {}\n\n  /**\n   * Assuming the base image is not a manifest list, checks and warns misconfigured platforms.\n   *\n   * @param buildContext the {@link BuildContext}\n   * @param containerConfig container configuration JSON of the base image\n   */\n  static void checkManifestPlatform(\n      BuildContext buildContext, ContainerConfigurationTemplate containerConfig)\n      throws PlatformNotFoundInBaseImageException {\n    Optional<Path> path = buildContext.getBaseImageConfiguration().getTarPath();\n    String baseImageName =\n        path.map(Path::toString)\n            .orElse(buildContext.getBaseImageConfiguration().getImage().toString());\n\n    Set<Platform> platforms = buildContext.getContainerConfiguration().getPlatforms();\n    Verify.verify(!platforms.isEmpty());\n\n    if (platforms.size() != 1) {\n      String msg =\n          String.format(\n              \"cannot build for multiple platforms since the base image '%s' is not a manifest list.\",\n              baseImageName);\n      throw new PlatformNotFoundInBaseImageException(msg);\n    } else {\n      Platform platform = platforms.iterator().next();\n      if (!platform.getArchitecture().equals(containerConfig.getArchitecture())\n          || !platform.getOs().equals(containerConfig.getOs())) {\n\n        // Unfortunately, \"platforms\" has amd64/linux by default even if the user didn't explicitly\n        // configure it. Skip reporting to suppress false alarm.\n        if (!(platform.getArchitecture().equals(\"amd64\") && platform.getOs().equals(\"linux\"))) {\n          String msg =\n              String.format(\n                  \"the configured platform (%s/%s) doesn't match the platform (%s/%s) of the base image (%s)\",\n                  platform.getArchitecture(),\n                  platform.getOs(),\n                  containerConfig.getArchitecture(),\n                  containerConfig.getOs(),\n                  baseImageName);\n          throw new PlatformNotFoundInBaseImageException(msg);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PreparedLayer.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.image.Layer;\n\n/**\n * Layer prepared from {@link BuildAndCacheApplicationLayerStep} and {@link\n * ObtainBaseImageLayerStep} to hold information about either a base image layer or an application\n * layer.\n */\nclass PreparedLayer implements Layer {\n\n  enum StateInTarget {\n    UNKNOWN,\n    EXISTING,\n    MISSING\n  }\n\n  static class Builder {\n\n    private Layer layer;\n    private String name = \"unnamed layer\";\n    private StateInTarget stateInTarget = StateInTarget.UNKNOWN;\n\n    Builder(Layer layer) {\n      this.layer = layer;\n    }\n\n    Builder setName(String name) {\n      this.name = name;\n      return this;\n    }\n\n    /** Sets whether the layer exists in a target destination. */\n    Builder setStateInTarget(StateInTarget stateInTarget) {\n      this.stateInTarget = stateInTarget;\n      return this;\n    }\n\n    PreparedLayer build() {\n      return new PreparedLayer(layer, name, stateInTarget);\n    }\n  }\n\n  private final Layer layer;\n  private final String name;\n  private final StateInTarget stateInTarget;\n\n  private PreparedLayer(Layer layer, String name, StateInTarget stateInTarget) {\n    this.layer = layer;\n    this.name = name;\n    this.stateInTarget = stateInTarget;\n  }\n\n  String getName() {\n    return name;\n  }\n\n  StateInTarget getStateInTarget() {\n    return stateInTarget;\n  }\n\n  @Override\n  public Blob getBlob() {\n    return layer.getBlob();\n  }\n\n  @Override\n  public BlobDescriptor getBlobDescriptor() {\n    return layer.getBlobDescriptor();\n  }\n\n  @Override\n  public DescriptorDigest getDiffId() {\n    return layer.getDiffId();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.LayerCountMismatchException;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.cloud.tools.jib.image.json.BadContainerConfigurationFormatException;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.JsonToImageTranslator;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException;\nimport com.google.cloud.tools.jib.image.json.UnknownManifestFormatException;\nimport com.google.cloud.tools.jib.image.json.UnlistedPlatformInManifestListException;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.ManifestAndDigest;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport javax.annotation.Nullable;\n\n/** Pulls the base image manifests for the specified platforms. */\nclass PullBaseImageStep implements Callable<ImagesAndRegistryClient> {\n\n  private static final String DESCRIPTION = \"Pulling base image manifest\";\n\n  /** Structure for the result returned by this step. */\n  static class ImagesAndRegistryClient {\n\n    final List<Image> images;\n    @Nullable final RegistryClient registryClient;\n\n    ImagesAndRegistryClient(List<Image> images, @Nullable RegistryClient registryClient) {\n      this.images = images;\n      this.registryClient = registryClient;\n    }\n  }\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressDispatcherFactory;\n\n  PullBaseImageStep(\n      BuildContext buildContext, ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    this.buildContext = buildContext;\n    this.progressDispatcherFactory = progressDispatcherFactory;\n  }\n\n  @Override\n  public ImagesAndRegistryClient call()\n      throws IOException, RegistryException, LayerPropertyNotFoundException,\n          LayerCountMismatchException, BadContainerConfigurationFormatException,\n          CacheCorruptedException, CredentialRetrievalException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (ProgressEventDispatcher progressDispatcher =\n            progressDispatcherFactory.create(\"pulling base image manifest\", 4);\n        TimerEventDispatcher ignored1 = new TimerEventDispatcher(eventHandlers, DESCRIPTION)) {\n\n      // Skip this step if this is a scratch image\n      ImageReference imageReference = buildContext.getBaseImageConfiguration().getImage();\n      if (imageReference.isScratch()) {\n        Set<Platform> platforms = buildContext.getContainerConfiguration().getPlatforms();\n        Verify.verify(!platforms.isEmpty());\n\n        eventHandlers.dispatch(LogEvent.progress(\"Getting scratch base image...\"));\n        ImmutableList.Builder<Image> images = ImmutableList.builder();\n        for (Platform platform : platforms) {\n          Image.Builder imageBuilder = Image.builder(buildContext.getTargetFormat());\n          imageBuilder.setArchitecture(platform.getArchitecture()).setOs(platform.getOs());\n          images.add(imageBuilder.build());\n        }\n        return new ImagesAndRegistryClient(images.build(), null);\n      }\n\n      eventHandlers.dispatch(\n          LogEvent.progress(\"Getting manifest for base image \" + imageReference + \"...\"));\n\n      if (buildContext.isOffline()) {\n        List<Image> images = getCachedBaseImages();\n        if (!images.isEmpty()) {\n          return new ImagesAndRegistryClient(images, null);\n        }\n        throw new IOException(\n            \"Cannot run Jib in offline mode; \" + imageReference + \" not found in local Jib cache\");\n\n      } else if (imageReference.getDigest().isPresent()) {\n        List<Image> images = getCachedBaseImages();\n        if (!images.isEmpty()) {\n          RegistryClient noAuthRegistryClient =\n              buildContext.newBaseImageRegistryClientFactory().newRegistryClient();\n          // TODO: passing noAuthRegistryClient may be problematic. It may return 401 unauthorized\n          // if layers have to be downloaded.\n          // https://github.com/GoogleContainerTools/jib/issues/2220\n          return new ImagesAndRegistryClient(images, noAuthRegistryClient);\n        }\n      }\n\n      Optional<ImagesAndRegistryClient> mirrorPull =\n          tryMirrors(buildContext, progressDispatcher.newChildProducer());\n      if (mirrorPull.isPresent()) {\n        return mirrorPull.get();\n      }\n\n      try {\n        // First, try with no credentials. This works with public GCR images (but not Docker Hub).\n        // TODO: investigate if we should just pass credentials up front. However, this involves\n        // some risk. https://github.com/GoogleContainerTools/jib/pull/2200#discussion_r359069026\n        // contains some related discussions.\n        RegistryClient noAuthRegistryClient =\n            buildContext.newBaseImageRegistryClientFactory().newRegistryClient();\n        return new ImagesAndRegistryClient(\n            pullBaseImages(noAuthRegistryClient, progressDispatcher.newChildProducer()),\n            noAuthRegistryClient);\n\n      } catch (RegistryUnauthorizedException ex) {\n        eventHandlers.dispatch(\n            LogEvent.lifecycle(\n                \"The base image requires auth. Trying again for \" + imageReference + \"...\"));\n\n        Credential credential =\n            RegistryCredentialRetriever.getBaseImageCredential(buildContext).orElse(null);\n        RegistryClient registryClient =\n            buildContext\n                .newBaseImageRegistryClientFactory()\n                .setCredential(credential)\n                .newRegistryClient();\n\n        String wwwAuthenticate = ex.getHttpResponseException().getHeaders().getAuthenticate();\n        if (wwwAuthenticate != null) {\n          eventHandlers.dispatch(\n              LogEvent.debug(\"WWW-Authenticate for \" + imageReference + \": \" + wwwAuthenticate));\n          registryClient.authPullByWwwAuthenticate(wwwAuthenticate);\n          return new ImagesAndRegistryClient(\n              pullBaseImages(registryClient, progressDispatcher.newChildProducer()),\n              registryClient);\n\n        } else {\n          // Not getting WWW-Authenticate is unexpected in practice, and we may just blame the\n          // server and fail. However, to keep some old behavior, try a few things as a last resort.\n          // TODO: consider removing this fallback branch.\n          if (credential != null && !credential.isOAuth2RefreshToken()) {\n            eventHandlers.dispatch(\n                LogEvent.debug(\"Trying basic auth as fallback for \" + imageReference + \"...\"));\n            registryClient.configureBasicAuth();\n            try {\n              return new ImagesAndRegistryClient(\n                  pullBaseImages(registryClient, progressDispatcher.newChildProducer()),\n                  registryClient);\n            } catch (RegistryUnauthorizedException ignored) {\n              // Fall back to try bearer auth.\n            }\n          }\n\n          eventHandlers.dispatch(\n              LogEvent.debug(\"Trying bearer auth as fallback for \" + imageReference + \"...\"));\n          registryClient.doPullBearerAuth();\n          return new ImagesAndRegistryClient(\n              pullBaseImages(registryClient, progressDispatcher.newChildProducer()),\n              registryClient);\n        }\n      }\n    }\n  }\n\n  @VisibleForTesting\n  Optional<ImagesAndRegistryClient> tryMirrors(\n      BuildContext buildContext, ProgressEventDispatcher.Factory progressDispatcherFactory)\n      throws LayerCountMismatchException, BadContainerConfigurationFormatException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n\n    Collection<Map.Entry<String, String>> mirrorEntries =\n        buildContext.getRegistryMirrors().entries();\n    try (ProgressEventDispatcher progressDispatcher1 =\n            progressDispatcherFactory.create(\"trying mirrors\", mirrorEntries.size());\n        TimerEventDispatcher ignored1 = new TimerEventDispatcher(eventHandlers, \"trying mirrors\")) {\n      for (Map.Entry<String, String> entry : mirrorEntries) {\n        String registry = entry.getKey();\n        String mirror = entry.getValue();\n        eventHandlers.dispatch(LogEvent.debug(\"mirror config: \" + registry + \" --> \" + mirror));\n\n        if (!buildContext.getBaseImageConfiguration().getImageRegistry().equals(registry)) {\n          progressDispatcher1.dispatchProgress(1);\n          continue;\n        }\n\n        eventHandlers.dispatch(LogEvent.info(\"trying mirror \" + mirror + \" for the base image\"));\n        try (ProgressEventDispatcher progressDispatcher2 =\n            progressDispatcher1.newChildProducer().create(\"trying mirror \" + mirror, 2)) {\n          RegistryClient registryClient =\n              buildContext.newBaseImageRegistryClientFactory(mirror).newRegistryClient();\n          List<Image> images = pullPublicImages(registryClient, progressDispatcher2);\n          eventHandlers.dispatch(LogEvent.info(\"pulled manifest from mirror \" + mirror));\n          return Optional.of(new ImagesAndRegistryClient(images, registryClient));\n\n        } catch (IOException | RegistryException ex) {\n          // Ignore errors from this mirror and continue.\n          eventHandlers.dispatch(\n              LogEvent.debug(\n                  \"failed to get manifest from mirror \" + mirror + \": \" + ex.getMessage()));\n        }\n      }\n      return Optional.empty();\n    }\n  }\n\n  private List<Image> pullPublicImages(\n      RegistryClient registryClient, ProgressEventDispatcher progressDispatcher)\n      throws IOException, RegistryException, LayerCountMismatchException,\n          BadContainerConfigurationFormatException {\n    try {\n      // First, try with no credentials. This works with public GCR images.\n      return pullBaseImages(registryClient, progressDispatcher.newChildProducer());\n\n    } catch (RegistryUnauthorizedException ex) {\n      // in case if a registry requires bearer auth\n      registryClient.doPullBearerAuth();\n      return pullBaseImages(registryClient, progressDispatcher.newChildProducer());\n    }\n  }\n\n  /**\n   * Pulls the base images specified in the platforms list.\n   *\n   * @param registryClient to communicate with remote registry\n   * @param progressDispatcherFactory the {@link ProgressEventDispatcher.Factory} for emitting\n   *     {@link ProgressEvent}s\n   * @return the list of pulled base images and a registry client\n   * @throws IOException when an I/O exception occurs during the pulling\n   * @throws RegistryException if communicating with the registry caused a known error\n   * @throws LayerCountMismatchException if the manifest and configuration contain conflicting layer\n   *     information\n   * @throws LayerPropertyNotFoundException if adding image layers fails\n   * @throws BadContainerConfigurationFormatException if the container configuration is in a bad\n   *     format\n   */\n  private List<Image> pullBaseImages(\n      RegistryClient registryClient, ProgressEventDispatcher.Factory progressDispatcherFactory)\n      throws IOException, RegistryException, LayerPropertyNotFoundException,\n          LayerCountMismatchException, BadContainerConfigurationFormatException {\n    Cache cache = buildContext.getBaseImageLayersCache();\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    ImageConfiguration baseImageConfig = buildContext.getBaseImageConfiguration();\n\n    try (ProgressEventDispatcher progressDispatcher1 =\n        progressDispatcherFactory.create(\"pulling base image manifest and container config\", 2)) {\n      ManifestAndDigest<?> manifestAndDigest =\n          registryClient.pullManifest(baseImageConfig.getImageQualifier());\n      eventHandlers.dispatch(\n          LogEvent.lifecycle(\"Using base image with digest: \" + manifestAndDigest.getDigest()));\n      progressDispatcher1.dispatchProgress(1);\n      ProgressEventDispatcher.Factory childProgressDispatcherFactory =\n          progressDispatcher1.newChildProducer();\n\n      ManifestTemplate manifestTemplate = manifestAndDigest.getManifest();\n      if (manifestTemplate instanceof V21ManifestTemplate) {\n        V21ManifestTemplate v21Manifest = (V21ManifestTemplate) manifestTemplate;\n        cache.writeMetadata(baseImageConfig.getImage(), v21Manifest);\n        return Collections.singletonList(JsonToImageTranslator.toImage(v21Manifest));\n\n      } else if (manifestTemplate instanceof BuildableManifestTemplate) {\n        // V22ManifestTemplate or OciManifestTemplate\n        BuildableManifestTemplate imageManifest = (BuildableManifestTemplate) manifestTemplate;\n        ContainerConfigurationTemplate containerConfig =\n            pullContainerConfigJson(\n                manifestAndDigest, registryClient, childProgressDispatcherFactory);\n        PlatformChecker.checkManifestPlatform(buildContext, containerConfig);\n        cache.writeMetadata(baseImageConfig.getImage(), imageManifest, containerConfig);\n        return Collections.singletonList(\n            JsonToImageTranslator.toImage(imageManifest, containerConfig));\n      }\n\n      Verify.verify(manifestTemplate instanceof ManifestListTemplate);\n\n      List<ManifestAndConfigTemplate> manifestsAndConfigs = new ArrayList<>();\n      ImmutableList.Builder<Image> images = ImmutableList.builder();\n      Set<Platform> platforms = buildContext.getContainerConfiguration().getPlatforms();\n      try (ProgressEventDispatcher progressDispatcher2 =\n          childProgressDispatcherFactory.create(\n              \"pulling platform-specific manifests and container configs\", 2L * platforms.size())) {\n        // If a manifest list, search for the manifests matching the given platforms.\n        for (Platform platform : platforms) {\n          String message = \"Searching for architecture=%s, os=%s in the base image manifest list\";\n          eventHandlers.dispatch(\n              LogEvent.info(String.format(message, platform.getArchitecture(), platform.getOs())));\n\n          String manifestDigest =\n              lookUpPlatformSpecificImageManifest(\n                  (ManifestListTemplate) manifestTemplate, platform);\n          // TODO: pull multiple manifests (+ container configs) in parallel.\n          ManifestAndDigest<?> imageManifestAndDigest = registryClient.pullManifest(manifestDigest);\n          progressDispatcher2.dispatchProgress(1);\n\n          BuildableManifestTemplate imageManifest =\n              (BuildableManifestTemplate) imageManifestAndDigest.getManifest();\n          ContainerConfigurationTemplate containerConfig =\n              pullContainerConfigJson(\n                  imageManifestAndDigest, registryClient, progressDispatcher2.newChildProducer());\n\n          manifestsAndConfigs.add(\n              new ManifestAndConfigTemplate(imageManifest, containerConfig, manifestDigest));\n          images.add(JsonToImageTranslator.toImage(imageManifest, containerConfig));\n        }\n      }\n\n      cache.writeMetadata(\n          baseImageConfig.getImage(),\n          new ImageMetadataTemplate(manifestTemplate /* manifest list */, manifestsAndConfigs));\n      return images.build();\n    }\n  }\n\n  /**\n   * Looks through a manifest list for the manifest matching the {@code platform} and returns the\n   * digest of the first manifest it finds.\n   */\n  @VisibleForTesting\n  String lookUpPlatformSpecificImageManifest(\n      ManifestListTemplate manifestListTemplate, Platform platform)\n      throws UnlistedPlatformInManifestListException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n\n    List<String> digests =\n        manifestListTemplate.getDigestsForPlatform(platform.getArchitecture(), platform.getOs());\n    if (digests.isEmpty()) {\n      String errorTemplate =\n          buildContext.getBaseImageConfiguration().getImage()\n              + \" is a manifest list, but the list does not contain an image for architecture=%s, \"\n              + \"os=%s. If your intention was to specify a platform for your image, see \"\n              + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#how-do-i-specify-a-platform-in-the-manifest-list-or-oci-index-of-a-base-image\";\n      String error = String.format(errorTemplate, platform.getArchitecture(), platform.getOs());\n      eventHandlers.dispatch(LogEvent.error(error));\n      throw new UnlistedPlatformInManifestListException(error);\n    }\n    // TODO: perhaps we should return multiple digests matching the platform.\n    return digests.get(0);\n  }\n\n  /**\n   * Pulls a container configuration JSON specified in the given manifest.\n   *\n   * @param manifestAndDigest a manifest JSON and its digest\n   * @param registryClient to communicate with remote registry\n   * @param progressDispatcherFactory the {@link ProgressEventDispatcher.Factory} for emitting\n   *     {@link ProgressEvent}s\n   * @return pulled {@link ContainerConfigurationTemplate}\n   * @throws IOException when an I/O exception occurs during the pulling\n   * @throws LayerPropertyNotFoundException if adding image layers fails\n   */\n  private ContainerConfigurationTemplate pullContainerConfigJson(\n      ManifestAndDigest<?> manifestAndDigest,\n      RegistryClient registryClient,\n      ProgressEventDispatcher.Factory progressDispatcherFactory)\n      throws IOException, LayerPropertyNotFoundException, UnknownManifestFormatException {\n    BuildableManifestTemplate manifest =\n        (BuildableManifestTemplate) manifestAndDigest.getManifest();\n    Preconditions.checkArgument(manifest.getSchemaVersion() == 2);\n\n    if (manifest.getContainerConfiguration() == null\n        || manifest.getContainerConfiguration().getDigest() == null) {\n      throw new UnknownManifestFormatException(\n          \"Invalid container configuration in Docker V2.2/OCI manifest: \\n\"\n              + JsonTemplateMapper.toUtf8String(manifest));\n    }\n\n    try (ThrottledProgressEventDispatcherWrapper progressDispatcherWrapper =\n        new ThrottledProgressEventDispatcherWrapper(\n            progressDispatcherFactory,\n            \"pull container configuration \" + manifest.getContainerConfiguration().getDigest())) {\n\n      String containerConfigString =\n          Blobs.writeToString(\n              registryClient.pullBlob(\n                  manifest.getContainerConfiguration().getDigest(),\n                  progressDispatcherWrapper::setProgressTarget,\n                  progressDispatcherWrapper::dispatchProgress));\n      return JsonTemplateMapper.readJson(\n          containerConfigString, ContainerConfigurationTemplate.class);\n    }\n  }\n\n  /**\n   * Retrieves the cached base images. If a base image reference is not a manifest list, returns a\n   * single image (if cached). If a manifest list, returns all the images matching the configured\n   * platforms in the manifest list but only when all of the images are cached.\n   *\n   * @return the cached images, if found\n   * @throws IOException when an I/O exception occurs\n   * @throws CacheCorruptedException if the cache is corrupted\n   * @throws LayerPropertyNotFoundException if adding image layers fails\n   * @throws BadContainerConfigurationFormatException if the container configuration is in a bad\n   *     format\n   * @throws UnlistedPlatformInManifestListException if a cached manifest list has no manifests\n   *     matching the configured platform\n   */\n  @VisibleForTesting\n  List<Image> getCachedBaseImages()\n      throws IOException, CacheCorruptedException, BadContainerConfigurationFormatException,\n          LayerCountMismatchException, UnlistedPlatformInManifestListException,\n          PlatformNotFoundInBaseImageException {\n    ImageReference baseImage = buildContext.getBaseImageConfiguration().getImage();\n    Optional<ImageMetadataTemplate> metadata =\n        buildContext.getBaseImageLayersCache().retrieveMetadata(baseImage);\n    if (!metadata.isPresent()) {\n      return Collections.emptyList();\n    }\n\n    ManifestTemplate manifestList = metadata.get().getManifestList();\n    List<ManifestAndConfigTemplate> manifestsAndConfigs = metadata.get().getManifestsAndConfigs();\n\n    if (manifestList == null) {\n      Verify.verify(manifestsAndConfigs.size() == 1);\n      ManifestAndConfigTemplate manifestAndConfig = manifestsAndConfigs.get(0);\n      Optional<Image> cachedImage = getBaseImageIfAllLayersCached(manifestAndConfig, true);\n      if (!cachedImage.isPresent()) {\n        return Collections.emptyList();\n      }\n      return Collections.singletonList(cachedImage.get());\n    }\n\n    // Manifest list cached. Identify matching platforms and check if all of them are cached.\n    ImmutableList.Builder<Image> images = ImmutableList.builder();\n    for (Platform platform : buildContext.getContainerConfiguration().getPlatforms()) {\n      String manifestDigest =\n          lookUpPlatformSpecificImageManifest((ManifestListTemplate) manifestList, platform);\n\n      Optional<ManifestAndConfigTemplate> manifestAndConfigFound =\n          manifestsAndConfigs.stream()\n              .filter(entry -> manifestDigest.equals(entry.getManifestDigest()))\n              .findFirst();\n      if (!manifestAndConfigFound.isPresent()) {\n        return Collections.emptyList();\n      }\n      Optional<Image> cachedImage =\n          getBaseImageIfAllLayersCached(manifestAndConfigFound.get(), false);\n      if (!cachedImage.isPresent()) {\n        return Collections.emptyList();\n      }\n      images.add(cachedImage.get());\n    }\n    return images.build();\n  }\n\n  /**\n   * Helper method to retrieve a base image from cache given manifest and container config. Does not\n   * return image if any layers of base image are missing in cache.\n   *\n   * @param manifestAndConfig stores an image manifest and a container config\n   * @param isSingleManifest true if base image is not a manifest list\n   * @return the single cached {@link Image} found, or {@code Optional#empty()} if the base image\n   *     not found in cache with all layers present.\n   * @throws BadContainerConfigurationFormatException if the container configuration is in a bad\n   *     format\n   * @throws PlatformNotFoundInBaseImageException if build target platform is not found in the base\n   *     image\n   * @throws LayerCountMismatchException LayerCountMismatchException if the manifest and\n   *     configuration contain conflicting layer information\n   */\n  private Optional<Image> getBaseImageIfAllLayersCached(\n      ManifestAndConfigTemplate manifestAndConfig, boolean isSingleManifest)\n      throws BadContainerConfigurationFormatException, PlatformNotFoundInBaseImageException,\n          LayerCountMismatchException {\n    Cache baseImageLayersCache = buildContext.getBaseImageLayersCache();\n    ManifestTemplate manifest = Verify.verifyNotNull(manifestAndConfig.getManifest());\n\n    // Verify all layers described in manifest are present in cache\n    if (!baseImageLayersCache.areAllLayersCached(manifest)) {\n      return Optional.empty();\n    }\n    if (manifest instanceof V21ManifestTemplate) {\n      return Optional.of(JsonToImageTranslator.toImage((V21ManifestTemplate) manifest));\n    }\n    ContainerConfigurationTemplate containerConfig =\n        Verify.verifyNotNull(manifestAndConfig.getConfig());\n    if (isSingleManifest) {\n      // If base image is not a manifest list, check and warn misconfigured platforms.\n      PlatformChecker.checkManifestPlatform(buildContext, containerConfig);\n    }\n    return Optional.of(\n        JsonToImageTranslator.toImage((BuildableManifestTemplate) manifest, containerConfig));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushBlobStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.progress.ThrottledAccumulatingConsumer;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport java.io.IOException;\nimport java.util.concurrent.Callable;\n\n/** Pushes a BLOB to the target registry. */\nclass PushBlobStep implements Callable<BlobDescriptor> {\n\n  private static final String DESCRIPTION = \"Pushing BLOB \";\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final RegistryClient registryClient;\n  private final BlobDescriptor blobDescriptor;\n  private final Blob blob;\n  private final boolean forcePush;\n\n  PushBlobStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      BlobDescriptor blobDescriptor,\n      Blob blob,\n      boolean forcePush) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.registryClient = registryClient;\n    this.blobDescriptor = blobDescriptor;\n    this.blob = blob;\n    this.forcePush = forcePush;\n  }\n\n  @Override\n  public BlobDescriptor call() throws IOException, RegistryException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    DescriptorDigest blobDigest = blobDescriptor.getDigest();\n    try (ProgressEventDispatcher progressEventDispatcher =\n            progressEventDispatcherFactory.create(\n                \"pushing blob \" + blobDigest, blobDescriptor.getSize());\n        TimerEventDispatcher ignored =\n            new TimerEventDispatcher(eventHandlers, DESCRIPTION + blobDescriptor);\n        ThrottledAccumulatingConsumer throttledProgressReporter =\n            new ThrottledAccumulatingConsumer(progressEventDispatcher::dispatchProgress)) {\n\n      // check if the BLOB is available\n      if (!forcePush && registryClient.checkBlob(blobDigest).isPresent()) {\n        eventHandlers.dispatch(\n            LogEvent.info(\n                \"Skipping push; BLOB already exists on target registry : \" + blobDescriptor));\n        return blobDescriptor;\n      }\n\n      // If base and target images are in the same registry, then use mount/from to try mounting the\n      // BLOB from the base image repository to the target image repository and possibly avoid\n      // having to push the BLOB. See\n      // https://docs.docker.com/registry/spec/api/#cross-repository-blob-mount for details.\n      String baseRegistry = buildContext.getBaseImageConfiguration().getImageRegistry();\n      String baseRepository = buildContext.getBaseImageConfiguration().getImageRepository();\n      String targetRegistry = buildContext.getTargetImageConfiguration().getImageRegistry();\n      String sourceRepository = targetRegistry.equals(baseRegistry) ? baseRepository : null;\n      registryClient.pushBlob(blobDigest, blob, sourceRepository, throttledProgressReporter);\n      return blobDescriptor;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushContainerConfigurationStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.ImageToJsonTranslator;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport java.io.IOException;\nimport java.util.concurrent.Callable;\n\n/** Pushes the container configuration. */\nclass PushContainerConfigurationStep implements Callable<BlobDescriptor> {\n\n  private static final String DESCRIPTION = \"Pushing container configuration\";\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final RegistryClient registryClient;\n  private final Image builtImage;\n\n  PushContainerConfigurationStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      Image builtImage) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.registryClient = registryClient;\n    this.builtImage = builtImage;\n  }\n\n  @Override\n  public BlobDescriptor call() throws IOException, RegistryException {\n    try (ProgressEventDispatcher progressEventDispatcher =\n            progressEventDispatcherFactory.create(\"pushing container configuration\", 1);\n        TimerEventDispatcher ignored =\n            new TimerEventDispatcher(buildContext.getEventHandlers(), DESCRIPTION)) {\n      JsonTemplate containerConfiguration =\n          new ImageToJsonTranslator(builtImage).getContainerConfiguration();\n\n      return new PushBlobStep(\n              buildContext,\n              progressEventDispatcher.newChildProducer(),\n              registryClient,\n              Digests.computeDigest(containerConfiguration),\n              Blobs.from(containerConfiguration),\n              false)\n          .call();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushImageStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageToJsonTranslator;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.stream.Collectors;\n\n/**\n * Pushes a manifest or a manifest list for a tag. If not a manifest list, returns the manifest\n * digest (\"image digest\") and the container configuration digest (\"image id\") as {@link\n * BuildResult}. If a manifest list, returns the manifest list digest only.\n */\n// TODO: figure out the right return value and type when pushing a manifest list.\nclass PushImageStep implements Callable<BuildResult> {\n\n  private static final String DESCRIPTION = \"Pushing manifest\";\n\n  static ImmutableList<PushImageStep> makeList(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      BlobDescriptor containerConfigurationDigestAndSize,\n      Image builtImage,\n      boolean manifestAlreadyExists)\n      throws IOException {\n    // Gets the image manifest to push.\n    BuildableManifestTemplate manifestTemplate =\n        new ImageToJsonTranslator(builtImage)\n            .getManifestTemplate(\n                buildContext.getTargetFormat(), containerConfigurationDigestAndSize);\n    DescriptorDigest manifestDigest = Digests.computeJsonDigest(manifestTemplate);\n    Set<String> imageQualifiers = getImageQualifiers(buildContext, builtImage, manifestDigest);\n\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (TimerEventDispatcher ignored =\n            new TimerEventDispatcher(eventHandlers, \"Preparing manifest pushers\");\n        ProgressEventDispatcher progressDispatcher =\n            progressEventDispatcherFactory.create(\n                \"launching manifest pushers\", imageQualifiers.size())) {\n\n      if (JibSystemProperties.skipExistingImages() && manifestAlreadyExists) {\n        eventHandlers.dispatch(LogEvent.info(\"Skipping pushing manifest; already exists.\"));\n        return ImmutableList.of();\n      }\n\n      return imageQualifiers.stream()\n          .map(\n              qualifier ->\n                  new PushImageStep(\n                      buildContext,\n                      progressDispatcher.newChildProducer(),\n                      registryClient,\n                      manifestTemplate,\n                      qualifier,\n                      manifestDigest,\n                      containerConfigurationDigestAndSize.getDigest()))\n          .collect(ImmutableList.toImmutableList());\n    }\n  }\n\n  private static Set<String> getImageQualifiers(\n      BuildContext buildContext, Image builtImage, DescriptorDigest manifestDigest) {\n    boolean singlePlatform = buildContext.getContainerConfiguration().getPlatforms().size() == 1;\n    Set<String> tags = buildContext.getAllTargetImageTags();\n    if (singlePlatform) {\n      return tags;\n    }\n    if (buildContext.getEnablePlatformTags()) {\n      String architecture = builtImage.getArchitecture();\n      return tags.stream().map(tag -> tag + \"-\" + architecture).collect(Collectors.toSet());\n    }\n    return Collections.singleton(manifestDigest.toString());\n  }\n\n  static ImmutableList<PushImageStep> makeListForManifestList(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      ManifestTemplate manifestList,\n      boolean manifestListAlreadyExists)\n      throws IOException {\n    Set<String> tags = buildContext.getAllTargetImageTags();\n\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (TimerEventDispatcher ignored =\n            new TimerEventDispatcher(eventHandlers, \"Preparing manifest list pushers\");\n        ProgressEventDispatcher progressEventDispatcher =\n            progressEventDispatcherFactory.create(\"launching manifest list pushers\", tags.size())) {\n      boolean singlePlatform = buildContext.getContainerConfiguration().getPlatforms().size() == 1;\n      if (singlePlatform) {\n        return ImmutableList.of(); // single image; no need to push a manifest list\n      }\n\n      if (JibSystemProperties.skipExistingImages() && manifestListAlreadyExists) {\n        eventHandlers.dispatch(LogEvent.info(\"Skipping pushing manifest list; already exists.\"));\n        return ImmutableList.of();\n      }\n      DescriptorDigest manifestListDigest = Digests.computeJsonDigest(manifestList);\n      return tags.stream()\n          .map(\n              tag ->\n                  new PushImageStep(\n                      buildContext,\n                      progressEventDispatcher.newChildProducer(),\n                      registryClient,\n                      manifestList,\n                      tag,\n                      manifestListDigest,\n                      // TODO: a manifest list digest isn't an \"image id\". Figure out the right\n                      // return value and type.\n                      manifestListDigest))\n          .collect(ImmutableList.toImmutableList());\n    }\n  }\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n  private final RegistryClient registryClient;\n  private final ManifestTemplate manifestTemplate;\n  private final String imageQualifier;\n  private final DescriptorDigest imageDigest;\n  private final DescriptorDigest imageId;\n\n  PushImageStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      ManifestTemplate manifestTemplate,\n      String imageQualifier,\n      DescriptorDigest imageDigest,\n      DescriptorDigest imageId) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.registryClient = registryClient;\n    this.manifestTemplate = manifestTemplate;\n    this.imageQualifier = imageQualifier;\n    this.imageDigest = imageDigest;\n    this.imageId = imageId;\n  }\n\n  @Override\n  public BuildResult call() throws IOException, RegistryException {\n    EventHandlers eventHandlers = buildContext.getEventHandlers();\n    try (TimerEventDispatcher ignored = new TimerEventDispatcher(eventHandlers, DESCRIPTION);\n        ProgressEventDispatcher ignored2 =\n            progressEventDispatcherFactory.create(\"pushing manifest for \" + imageQualifier, 1)) {\n      eventHandlers.dispatch(LogEvent.info(\"Pushing manifest for \" + imageQualifier + \"...\"));\n\n      registryClient.pushManifest(manifestTemplate, imageQualifier);\n      return new BuildResult(imageDigest, imageId, true);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushLayerStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PreparedLayer.StateInTarget;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\n\nclass PushLayerStep implements Callable<BlobDescriptor> {\n\n  static ImmutableList<PushLayerStep> makeList(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      List<Future<PreparedLayer>> cachedLayers) {\n    try (TimerEventDispatcher ignored =\n            new TimerEventDispatcher(buildContext.getEventHandlers(), \"Preparing layer pushers\");\n        ProgressEventDispatcher progressEventDispatcher =\n            progressEventDispatcherFactory.create(\"launching layer pushers\", cachedLayers.size())) {\n\n      // Constructs a PushBlobStep for each layer.\n      return cachedLayers.stream()\n          .map(\n              layer ->\n                  new PushLayerStep(\n                      buildContext,\n                      progressEventDispatcher.newChildProducer(),\n                      registryClient,\n                      layer))\n          .collect(ImmutableList.toImmutableList());\n    }\n  }\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final RegistryClient registryClient;\n  private final Future<PreparedLayer> preparedLayer;\n\n  private PushLayerStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      RegistryClient registryClient,\n      Future<PreparedLayer> preparedLayer) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.registryClient = registryClient;\n    this.preparedLayer = preparedLayer;\n  }\n\n  @Override\n  public BlobDescriptor call()\n      throws IOException, RegistryException, ExecutionException, InterruptedException {\n    PreparedLayer layer = preparedLayer.get();\n\n    if (layer.getStateInTarget() == StateInTarget.EXISTING) {\n      return layer.getBlobDescriptor(); // skip pushing if known to exist in registry\n    }\n\n    boolean forcePush = layer.getStateInTarget() == StateInTarget.MISSING;\n    return new PushBlobStep(\n            buildContext,\n            progressEventDispatcherFactory,\n            registryClient,\n            layer.getBlobDescriptor(),\n            layer.getBlob(),\n            forcePush)\n        .call();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/RegistryCredentialRetriever.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport java.util.Optional;\n\n/** Attempts to retrieve registry credentials. */\nclass RegistryCredentialRetriever {\n\n  private RegistryCredentialRetriever() {}\n\n  /** Retrieves credentials for the base image. */\n  static Optional<Credential> getBaseImageCredential(BuildContext buildContext)\n      throws CredentialRetrievalException {\n    return retrieve(buildContext.getBaseImageConfiguration(), buildContext.getEventHandlers());\n  }\n\n  /** Retrieves credentials for the target image. */\n  static Optional<Credential> getTargetImageCredential(BuildContext buildContext)\n      throws CredentialRetrievalException {\n    return retrieve(buildContext.getTargetImageConfiguration(), buildContext.getEventHandlers());\n  }\n\n  private static Optional<Credential> retrieve(\n      ImageConfiguration imageConfiguration, EventHandlers eventHandlers)\n      throws CredentialRetrievalException {\n    for (CredentialRetriever retriever : imageConfiguration.getCredentialRetrievers()) {\n      Optional<Credential> credential = retriever.retrieve();\n      if (credential.isPresent()) {\n        return credential;\n      }\n    }\n\n    String registry = imageConfiguration.getImageRegistry();\n    String repository = imageConfiguration.getImageRepository();\n    eventHandlers.dispatch(\n        LogEvent.info(\"No credentials could be retrieved for \" + registry + \"/\" + repository));\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.DockerInfoDetails;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage;\nimport com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.ManifestAndDigest;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\n/**\n * Runs steps for building an image.\n *\n * <p>Use by first calling {@link #begin} and then calling the individual step running methods. Note\n * that order matters, so make sure that steps are run before other steps that depend on them. Wait\n * on the last step by calling the respective {@code wait...} methods.\n */\npublic class StepsRunner {\n\n  /** Holds the individual step results. */\n  private static class StepResults {\n\n    private static <E> Future<E> failedFuture() {\n      return Futures.immediateFailedFuture(\n          new IllegalStateException(\"invalid usage; required step not configured\"));\n    }\n\n    @Nullable private List<Future<PreparedLayer>> applicationLayers;\n    private Future<ManifestTemplate> manifestListOrSingleManifest = failedFuture();\n    private Future<RegistryClient> targetRegistryClient = failedFuture();\n    private Future<List<Future<BlobDescriptor>>> applicationLayerPushResults = failedFuture();\n    private Future<Optional<ManifestAndDigest<ManifestTemplate>>> manifestCheckResult =\n        failedFuture();\n    private Future<List<Future<BuildResult>>> imagePushResults = failedFuture();\n    private Future<BuildResult> buildResult = failedFuture();\n\n    private Future<ImagesAndRegistryClient> baseImagesAndRegistryClient = failedFuture();\n    private Future<Map<Image, List<Future<PreparedLayer>>>> baseImagesAndLayers = failedFuture();\n    private Future<Map<Image, List<Future<BlobDescriptor>>>> baseImagesAndLayerPushResults =\n        failedFuture();\n    private Future<Map<Image, Future<BlobDescriptor>>> baseImagesAndContainerConfigPushResults =\n        failedFuture();\n    private Future<Map<Image, Future<Image>>> baseImagesAndBuiltImages = failedFuture();\n  }\n\n  /**\n   * Starts building the steps to run.\n   *\n   * @param buildContext the {@link BuildContext}\n   * @return a new {@link StepsRunner}\n   */\n  public static StepsRunner begin(BuildContext buildContext) {\n    ExecutorService executorService =\n        JibSystemProperties.serializeExecution()\n            ? MoreExecutors.newDirectExecutorService()\n            : buildContext.getExecutorService();\n\n    return new StepsRunner(MoreExecutors.listeningDecorator(executorService), buildContext);\n  }\n\n  private static <E> List<E> realizeFutures(Collection<Future<E>> futures)\n      throws InterruptedException, ExecutionException {\n    List<E> values = new ArrayList<>();\n    for (Future<E> future : futures) {\n      values.add(future.get());\n    }\n    return values;\n  }\n\n  private final StepResults results = new StepResults();\n\n  private final ExecutorService executorService;\n  private final BuildContext buildContext;\n  private final TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n\n  // Instead of directly running each step, we first save them as a lambda. This is only because of\n  // the unfortunate chicken-and-egg situation when using ProgressEventDispatcher. The current\n  // ProgressEventDispatcher model requires allocating the total units of work (i.e., steps)\n  // up front. That is, to instantiate a root ProgressEventDispatcher, we should know ahead how many\n  // steps we will run. However, to run a step, we need a root progress dispatcher. So, we take each\n  // step as a lambda and save them to run later. Then we can count the number of lambdas, create a\n  // root dispatcher with the count, and run the saved lambdas using the dispatcher.\n  private final List<Consumer<ProgressEventDispatcher.Factory>> stepsToRun = new ArrayList<>();\n\n  @Nullable private String rootProgressDescription;\n\n  @VisibleForTesting\n  StepsRunner(ListeningExecutorService executorService, BuildContext buildContext) {\n    this.executorService = executorService;\n    this.buildContext = buildContext;\n  }\n\n  /**\n   * Add steps for loading an image to docker daemon.\n   *\n   * @param dockerClient the docker client to load the image to\n   * @return this\n   */\n  public StepsRunner dockerLoadSteps(DockerClient dockerClient) {\n    rootProgressDescription = \"building image to Docker daemon\";\n\n    addRetrievalSteps(true); // always pull layers for docker builds\n    stepsToRun.add(this::buildAndCacheApplicationLayers);\n    stepsToRun.add(this::buildImages);\n\n    // load to Docker\n    stepsToRun.add(\n        progressDispatcherFactory -> loadDocker(dockerClient, progressDispatcherFactory));\n    return this;\n  }\n\n  /**\n   * Add steps for writing an image as a tar file archive.\n   *\n   * @param outputPath the target file path to write the image to\n   * @return this\n   */\n  public StepsRunner tarBuildSteps(Path outputPath) {\n    rootProgressDescription = \"building image to tar file\";\n\n    addRetrievalSteps(true); // always pull layers for tar builds\n    stepsToRun.add(this::buildAndCacheApplicationLayers);\n    stepsToRun.add(this::buildImages);\n\n    // create a tar\n    stepsToRun.add(\n        progressDispatcherFactory -> writeTarFile(outputPath, progressDispatcherFactory));\n    return this;\n  }\n\n  /**\n   * Add steps for pushing images to a remote registry. The registry is determined by the image\n   * name.\n   *\n   * @return this\n   */\n  public StepsRunner registryPushSteps() {\n    rootProgressDescription = \"building images to registry\";\n    boolean layersRequiredLocally = buildContext.getAlwaysCacheBaseImage();\n\n    stepsToRun.add(this::authenticateBearerPush);\n\n    addRetrievalSteps(layersRequiredLocally);\n    stepsToRun.add(this::buildAndCacheApplicationLayers);\n    stepsToRun.add(this::buildImages);\n    stepsToRun.add(this::buildManifestListOrSingleManifest);\n\n    // push to registry\n    stepsToRun.add(this::pushBaseImagesLayers);\n    stepsToRun.add(this::pushApplicationLayers);\n    stepsToRun.add(this::pushContainerConfigurations);\n    stepsToRun.add(this::checkManifestInTargetRegistry);\n    stepsToRun.add(this::pushImages);\n    stepsToRun.add(this::pushManifestList);\n    return this;\n  }\n\n  /**\n   * Run all steps and return a BuildResult after a build is completed.\n   *\n   * @return a {@link BuildResult} with build metadata\n   * @throws ExecutionException if an error occurred during asynchronous execution of steps\n   * @throws InterruptedException if the build was interrupted while waiting for results\n   */\n  public BuildResult run() throws ExecutionException, InterruptedException {\n    Preconditions.checkNotNull(rootProgressDescription);\n\n    try (ProgressEventDispatcher progressEventDispatcher =\n        ProgressEventDispatcher.newRoot(\n            buildContext.getEventHandlers(), rootProgressDescription, stepsToRun.size())) {\n      stepsToRun.forEach(step -> step.accept(progressEventDispatcher.newChildProducer()));\n      return results.buildResult.get();\n\n    } catch (ExecutionException ex) {\n      ExecutionException unrolled = ex;\n      while (unrolled.getCause() instanceof ExecutionException) {\n        unrolled = (ExecutionException) unrolled.getCause();\n      }\n      throw unrolled;\n\n    } finally {\n      tempDirectoryProvider.close();\n    }\n  }\n\n  private void addRetrievalSteps(boolean layersRequiredLocally) {\n    ImageConfiguration baseImageConfiguration = buildContext.getBaseImageConfiguration();\n\n    if (baseImageConfiguration.getTarPath().isPresent()) {\n      // If tarPath is present, a TarImage was used\n      stepsToRun.add(this::extractTar);\n\n    } else if (baseImageConfiguration.getDockerClient().isPresent()) {\n      // If dockerClient is present, a DockerDaemonImage was used\n      stepsToRun.add(this::saveDocker);\n\n    } else {\n      // Otherwise default to RegistryImage\n      stepsToRun.add(this::pullBaseImages);\n      stepsToRun.add(\n          progressDispatcherFactory ->\n              obtainBaseImagesLayers(layersRequiredLocally, progressDispatcherFactory));\n    }\n  }\n\n  private void authenticateBearerPush(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.targetRegistryClient =\n        executorService.submit(new AuthenticatePushStep(buildContext, progressDispatcherFactory));\n  }\n\n  private void saveDocker(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    Optional<DockerClient> dockerClient =\n        buildContext.getBaseImageConfiguration().getDockerClient();\n    Preconditions.checkArgument(dockerClient.isPresent());\n\n    assignLocalImageResult(\n        executorService.submit(\n            LocalBaseImageSteps.retrieveDockerDaemonLayersStep(\n                buildContext,\n                progressDispatcherFactory,\n                dockerClient.get(),\n                tempDirectoryProvider)));\n  }\n\n  private void extractTar(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    Optional<Path> tarPath = buildContext.getBaseImageConfiguration().getTarPath();\n    Preconditions.checkArgument(tarPath.isPresent());\n\n    assignLocalImageResult(\n        executorService.submit(\n            LocalBaseImageSteps.retrieveTarLayersStep(\n                buildContext, progressDispatcherFactory, tarPath.get(), tempDirectoryProvider)));\n  }\n\n  private void assignLocalImageResult(Future<LocalImage> localImage) {\n    results.baseImagesAndRegistryClient =\n        executorService.submit(\n            () ->\n                LocalBaseImageSteps.returnImageAndRegistryClientStep(\n                        realizeFutures(localImage.get().layers),\n                        localImage.get().configurationTemplate)\n                    .call());\n\n    results.baseImagesAndLayers =\n        executorService.submit(\n            () ->\n                Collections.singletonMap(\n                    results.baseImagesAndRegistryClient.get().images.get(0),\n                    localImage.get().layers));\n  }\n\n  @VisibleForTesting\n  void pullBaseImages(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.baseImagesAndRegistryClient =\n        executorService.submit(new PullBaseImageStep(buildContext, progressDispatcherFactory));\n  }\n\n  private void obtainBaseImagesLayers(\n      boolean layersRequiredLocally, ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.baseImagesAndLayers =\n        executorService.submit(\n            () -> {\n              try (ProgressEventDispatcher progressDispatcher =\n                  progressDispatcherFactory.create(\n                      \"scheduling obtaining base images layers\",\n                      results.baseImagesAndRegistryClient.get().images.size())) {\n\n                Map<DescriptorDigest, Future<PreparedLayer>> preparedLayersCache = new HashMap<>();\n                Map<Image, List<Future<PreparedLayer>>> baseImagesAndLayers = new LinkedHashMap<>();\n                for (Image baseImage : results.baseImagesAndRegistryClient.get().images) {\n                  List<Future<PreparedLayer>> layers =\n                      obtainBaseImageLayers(\n                          baseImage,\n                          layersRequiredLocally,\n                          preparedLayersCache,\n                          progressDispatcher.newChildProducer());\n                  baseImagesAndLayers.put(baseImage, layers);\n                }\n                return baseImagesAndLayers;\n              }\n            });\n  }\n\n  // This method updates the given \"preparedLayersCache\" and should not be called concurrently.\n  @VisibleForTesting\n  List<Future<PreparedLayer>> obtainBaseImageLayers(\n      Image baseImage,\n      boolean layersRequiredLocally,\n      Map<DescriptorDigest, Future<PreparedLayer>> preparedLayersCache,\n      ProgressEventDispatcher.Factory progressDispatcherFactory)\n      throws InterruptedException, ExecutionException {\n    List<Future<PreparedLayer>> preparedLayers = new ArrayList<>();\n\n    try (ProgressEventDispatcher progressDispatcher =\n        progressDispatcherFactory.create(\n            \"launching base image layer pullers\", baseImage.getLayers().size())) {\n      for (Layer layer : baseImage.getLayers()) {\n        DescriptorDigest digest = layer.getBlobDescriptor().getDigest();\n        Future<PreparedLayer> preparedLayer = preparedLayersCache.get(digest);\n\n        if (preparedLayer != null) {\n          progressDispatcher.dispatchProgress(1);\n        } else { // If we haven't obtained this layer yet, launcher a puller.\n          preparedLayer =\n              executorService.submit(\n                  layersRequiredLocally\n                      ? ObtainBaseImageLayerStep.forForcedDownload(\n                          buildContext,\n                          progressDispatcher.newChildProducer(),\n                          layer,\n                          results.baseImagesAndRegistryClient.get().registryClient)\n                      : ObtainBaseImageLayerStep.forSelectiveDownload(\n                          buildContext,\n                          progressDispatcher.newChildProducer(),\n                          layer,\n                          results.baseImagesAndRegistryClient.get().registryClient,\n                          results.targetRegistryClient.get()));\n          preparedLayersCache.put(digest, preparedLayer);\n        }\n        preparedLayers.add(preparedLayer);\n      }\n      return preparedLayers;\n    }\n  }\n\n  private void pushBaseImagesLayers(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.baseImagesAndLayerPushResults =\n        executorService.submit(\n            () -> {\n              try (ProgressEventDispatcher progressDispatcher =\n                  progressDispatcherFactory.create(\n                      \"scheduling pushing base images layers\",\n                      results.baseImagesAndLayers.get().size())) {\n\n                Map<Image, List<Future<BlobDescriptor>>> layerPushResults = new LinkedHashMap<>();\n                for (Map.Entry<Image, List<Future<PreparedLayer>>> entry :\n                    results.baseImagesAndLayers.get().entrySet()) {\n                  Image baseImage = entry.getKey();\n                  List<Future<PreparedLayer>> baseLayers = entry.getValue();\n\n                  List<Future<BlobDescriptor>> pushResults =\n                      pushBaseImageLayers(baseLayers, progressDispatcher.newChildProducer());\n                  layerPushResults.put(baseImage, pushResults);\n                }\n                return layerPushResults;\n              }\n            });\n  }\n\n  private List<Future<BlobDescriptor>> pushBaseImageLayers(\n      List<Future<PreparedLayer>> baseLayers,\n      ProgressEventDispatcher.Factory progressDispatcherFactory)\n      throws InterruptedException, ExecutionException {\n    return scheduleCallables(\n        PushLayerStep.makeList(\n            buildContext,\n            progressDispatcherFactory,\n            results.targetRegistryClient.get(),\n            baseLayers));\n  }\n\n  private void buildAndCacheApplicationLayers(\n      ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.applicationLayers =\n        scheduleCallables(\n            BuildAndCacheApplicationLayerStep.makeList(buildContext, progressDispatcherFactory));\n  }\n\n  @VisibleForTesting\n  void buildImages(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.baseImagesAndBuiltImages =\n        executorService.submit(\n            () -> {\n              try (ProgressEventDispatcher progressDispatcher =\n                  progressDispatcherFactory.create(\n                      \"scheduling building manifests\", results.baseImagesAndLayers.get().size())) {\n\n                Map<Image, Future<Image>> baseImagesAndBuiltImages = new LinkedHashMap<>();\n                for (Map.Entry<Image, List<Future<PreparedLayer>>> entry :\n                    results.baseImagesAndLayers.get().entrySet()) {\n                  Image baseImage = entry.getKey();\n                  List<Future<PreparedLayer>> baseLayers = entry.getValue();\n\n                  Future<Image> builtImage =\n                      buildImage(baseImage, baseLayers, progressDispatcher.newChildProducer());\n                  baseImagesAndBuiltImages.put(baseImage, builtImage);\n                }\n                return baseImagesAndBuiltImages;\n              }\n            });\n  }\n\n  private Future<Image> buildImage(\n      Image baseImage,\n      List<Future<PreparedLayer>> baseLayers,\n      ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    return executorService.submit(\n        () ->\n            new BuildImageStep(\n                    buildContext,\n                    progressDispatcherFactory,\n                    baseImage,\n                    realizeFutures(baseLayers),\n                    realizeFutures(Verify.verifyNotNull(results.applicationLayers)))\n                .call());\n  }\n\n  private void buildManifestListOrSingleManifest(\n      ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.manifestListOrSingleManifest =\n        executorService.submit(\n            () ->\n                new BuildManifestListOrSingleManifestStep(\n                        buildContext,\n                        progressDispatcherFactory,\n                        realizeFutures(results.baseImagesAndBuiltImages.get().values()))\n                    .call());\n  }\n\n  private void pushContainerConfigurations(\n      ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.baseImagesAndContainerConfigPushResults =\n        executorService.submit(\n            () -> {\n              try (ProgressEventDispatcher progressDispatcher =\n                  progressDispatcherFactory.create(\n                      \"scheduling pushing container configurations\",\n                      results.baseImagesAndBuiltImages.get().size())) {\n\n                Map<Image, Future<BlobDescriptor>> configPushResults = new LinkedHashMap<>();\n                for (Map.Entry<Image, Future<Image>> entry :\n                    results.baseImagesAndBuiltImages.get().entrySet()) {\n                  Image baseImage = entry.getKey();\n                  Future<Image> builtImage = entry.getValue();\n\n                  Future<BlobDescriptor> pushResult =\n                      pushContainerConfiguration(builtImage, progressDispatcher.newChildProducer());\n                  configPushResults.put(baseImage, pushResult);\n                }\n                return configPushResults;\n              }\n            });\n  }\n\n  private Future<BlobDescriptor> pushContainerConfiguration(\n      Future<Image> builtImage, ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    return executorService.submit(\n        () ->\n            new PushContainerConfigurationStep(\n                    buildContext,\n                    progressDispatcherFactory,\n                    results.targetRegistryClient.get(),\n                    builtImage.get())\n                .call());\n  }\n\n  private void pushApplicationLayers(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.applicationLayerPushResults =\n        executorService.submit(\n            () ->\n                scheduleCallables(\n                    PushLayerStep.makeList(\n                        buildContext,\n                        progressDispatcherFactory,\n                        results.targetRegistryClient.get(),\n                        Verify.verifyNotNull(results.applicationLayers))));\n  }\n\n  private void checkManifestInTargetRegistry(\n      ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.manifestCheckResult =\n        executorService.submit(\n            () ->\n                new CheckManifestStep(\n                        buildContext,\n                        progressDispatcherFactory,\n                        results.targetRegistryClient.get(),\n                        results.manifestListOrSingleManifest.get())\n                    .call());\n  }\n\n  private void pushImages(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.imagePushResults =\n        executorService.submit(\n            () -> {\n              try (ProgressEventDispatcher progressDispatcher =\n                  progressDispatcherFactory.create(\n                      \"scheduling pushing manifests\",\n                      results.baseImagesAndBuiltImages.get().size())) {\n\n                realizeFutures(results.applicationLayerPushResults.get());\n\n                List<Future<BuildResult>> buildResults = new ArrayList<>();\n                for (Map.Entry<Image, Future<Image>> entry :\n                    results.baseImagesAndBuiltImages.get().entrySet()) {\n                  Image baseImage = entry.getKey();\n                  Future<Image> builtImage = entry.getValue();\n\n                  buildResults.add(\n                      pushImage(baseImage, builtImage, progressDispatcher.newChildProducer()));\n                }\n                return buildResults;\n              }\n            });\n  }\n\n  private Future<BuildResult> pushImage(\n      Image baseImage,\n      Future<Image> builtImage,\n      ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    return executorService.submit(\n        () -> {\n          realizeFutures(\n              Verify.verifyNotNull(results.baseImagesAndLayerPushResults.get().get(baseImage)));\n\n          Future<BlobDescriptor> containerConfigPushResult =\n              results.baseImagesAndContainerConfigPushResults.get().get(baseImage);\n\n          List<Future<BuildResult>> manifestPushResults =\n              scheduleCallables(\n                  PushImageStep.makeList(\n                      buildContext,\n                      progressDispatcherFactory,\n                      results.targetRegistryClient.get(),\n                      Verify.verifyNotNull(containerConfigPushResult).get(),\n                      builtImage.get(),\n                      results.manifestCheckResult.get().isPresent()));\n\n          realizeFutures(manifestPushResults);\n\n          return manifestPushResults.isEmpty()\n              ? new BuildResult(\n                  results.manifestCheckResult.get().get().getDigest(),\n                  Verify.verifyNotNull(containerConfigPushResult).get().getDigest(),\n                  isImagePushed(results.manifestCheckResult.get()))\n              // Manifest pushers return the same BuildResult.\n              : manifestPushResults.get(0).get();\n        });\n  }\n\n  @VisibleForTesting\n  boolean isImagePushed(Optional<ManifestAndDigest<ManifestTemplate>> manifestResult) {\n\n    return !(JibSystemProperties.skipExistingImages() && manifestResult.isPresent());\n  }\n\n  private void pushManifestList(ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.buildResult =\n        executorService.submit(\n            () -> {\n              realizeFutures(results.imagePushResults.get());\n              List<Future<BuildResult>> manifestListPushResults =\n                  scheduleCallables(\n                      PushImageStep.makeListForManifestList(\n                          buildContext,\n                          progressDispatcherFactory,\n                          results.targetRegistryClient.get(),\n                          results.manifestListOrSingleManifest.get(),\n                          results.manifestCheckResult.get().isPresent()));\n\n              realizeFutures(manifestListPushResults);\n              return manifestListPushResults.isEmpty()\n                  ? results.imagePushResults.get().get(0).get()\n                  : manifestListPushResults.get(0).get();\n            });\n  }\n\n  private void loadDocker(\n      DockerClient dockerClient, ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.buildResult =\n        executorService.submit(\n            () -> {\n              DockerInfoDetails dockerInfoDetails = dockerClient.info();\n              String osType = dockerInfoDetails.getOsType();\n              String architecture = normalizeArchitecture(dockerInfoDetails.getArchitecture());\n              Image builtImage =\n                  fetchBuiltImageForLocalBuild(\n                      osType, architecture, buildContext.getEventHandlers());\n              return new LoadDockerStep(\n                      buildContext, progressDispatcherFactory, dockerClient, builtImage)\n                  .call();\n            });\n  }\n\n  private void writeTarFile(\n      Path outputPath, ProgressEventDispatcher.Factory progressDispatcherFactory) {\n    results.buildResult =\n        executorService.submit(\n            () -> {\n              Verify.verify(\n                  results.baseImagesAndBuiltImages.get().size() == 1,\n                  \"multi-platform image building not supported when building a local tar image\");\n              Image builtImage =\n                  results.baseImagesAndBuiltImages.get().values().iterator().next().get();\n\n              return new WriteTarFileStep(\n                      buildContext, progressDispatcherFactory, outputPath, builtImage)\n                  .call();\n            });\n  }\n\n  private <E> List<Future<E>> scheduleCallables(ImmutableList<? extends Callable<E>> callables) {\n    return callables.stream().map(executorService::submit).collect(Collectors.toList());\n  }\n\n  @VisibleForTesting\n  String normalizeArchitecture(String architecture) {\n    // Create mapping based on https://docs.docker.com/engine/install/#supported-platforms\n    if (architecture.equals(\"x86_64\")) {\n      return \"amd64\";\n    } else if (architecture.equals(\"aarch64\")) {\n      return \"arm64\";\n    }\n    return architecture;\n  }\n\n  @VisibleForTesting\n  Image fetchBuiltImageForLocalBuild(\n      String osType, String architecture, EventHandlers eventHandlers)\n      throws InterruptedException, ExecutionException {\n    if (results.baseImagesAndBuiltImages.get().size() > 1) {\n      eventHandlers.dispatch(\n          LogEvent.warn(\n              String.format(\n                  \"Detected multi-platform configuration, only building image that matches the local Docker Engine's os and architecture (%s/%s) or \"\n                      + \"the first platform specified\",\n                  osType, architecture)));\n      for (Map.Entry<Image, Future<Image>> imageEntry :\n          results.baseImagesAndBuiltImages.get().entrySet()) {\n        Image image = imageEntry.getValue().get();\n        if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) {\n          return image;\n        }\n      }\n    }\n    return results.baseImagesAndBuiltImages.get().values().iterator().next().get();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/ThrottledProgressEventDispatcherWrapper.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.event.progress.ThrottledAccumulatingConsumer;\nimport com.google.common.base.Preconditions;\nimport java.io.Closeable;\nimport javax.annotation.Nullable;\n\n/**\n * Contains a {@link ProgressEventDispatcher} and throttles dispatching progress events with the\n * default delay used by {@link ThrottledAccumulatingConsumer}. This class is mutable and should\n * only be used within a local context.\n *\n * <p>This class is necessary because the total BLOb size (allocation units) is not known until the\n * response headers are received, only after which can the {@link ProgressEventDispatcher} be\n * created.\n */\nclass ThrottledProgressEventDispatcherWrapper implements Closeable {\n\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n  private final String description;\n  @Nullable private ProgressEventDispatcher progressEventDispatcher;\n  @Nullable private ThrottledAccumulatingConsumer throttledDispatcher;\n\n  ThrottledProgressEventDispatcherWrapper(\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory, String description) {\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.description = description;\n  }\n\n  public void dispatchProgress(Long progressUnits) {\n    Preconditions.checkNotNull(throttledDispatcher);\n    throttledDispatcher.accept(progressUnits);\n  }\n\n  @Override\n  public void close() {\n    Preconditions.checkNotNull(progressEventDispatcher);\n    Preconditions.checkNotNull(throttledDispatcher);\n    throttledDispatcher.close();\n    progressEventDispatcher.close();\n  }\n\n  void setProgressTarget(long allocationUnits) {\n    Preconditions.checkState(progressEventDispatcher == null);\n    progressEventDispatcher = progressEventDispatcherFactory.create(description, allocationUnits);\n    throttledDispatcher =\n        new ThrottledAccumulatingConsumer(progressEventDispatcher::dispatchProgress);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/WriteTarFileStep.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.Callable;\n\npublic class WriteTarFileStep implements Callable<BuildResult> {\n\n  private final BuildContext buildContext;\n  private final ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n\n  private final Path outputPath;\n  private final Image builtImage;\n\n  WriteTarFileStep(\n      BuildContext buildContext,\n      ProgressEventDispatcher.Factory progressEventDispatcherFactory,\n      Path outputPath,\n      Image builtImage) {\n    this.buildContext = buildContext;\n    this.progressEventDispatcherFactory = progressEventDispatcherFactory;\n    this.outputPath = outputPath;\n    this.builtImage = builtImage;\n  }\n\n  @Override\n  public BuildResult call() throws IOException {\n    buildContext.getEventHandlers().dispatch(LogEvent.progress(\"Building image to tar file...\"));\n\n    try (ProgressEventDispatcher ignored =\n        progressEventDispatcherFactory.create(\"writing to tar file\", 1)) {\n      // Builds the image to a tarball.\n      if (outputPath.getParent() != null) {\n        Files.createDirectories(outputPath.getParent());\n      }\n      try (OutputStream outputStream =\n          new BufferedOutputStream(Files.newOutputStream(outputPath))) {\n        new ImageTarball(\n                builtImage,\n                buildContext.getTargetImageConfiguration().getImage(),\n                buildContext.getAllTargetImageTags())\n            .writeTo(outputStream);\n      }\n\n      return BuildResult.fromImage(builtImage, buildContext.getTargetFormat());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/Cache.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.concurrent.Immutable;\n\n/**\n * Cache for storing data to be shared between Jib executions.\n *\n * <p>This class is immutable and safe to use across threads.\n */\n@Immutable\npublic class Cache {\n\n  /**\n   * Initializes the cache using {@code cacheDirectory} for storage.\n   *\n   * @param cacheDirectory the directory for the cache. Creates the directory if it does not exist.\n   * @return a new {@link Cache}\n   * @throws CacheDirectoryCreationException if an I/O exception occurs when creating cache\n   *     directory\n   */\n  public static Cache withDirectory(Path cacheDirectory) throws CacheDirectoryCreationException {\n    try {\n      Files.createDirectories(cacheDirectory);\n    } catch (IOException ex) {\n      throw new CacheDirectoryCreationException(ex);\n    }\n    return new Cache(new CacheStorageFiles(cacheDirectory));\n  }\n\n  private final CacheStorageWriter cacheStorageWriter;\n  private final CacheStorageReader cacheStorageReader;\n\n  private Cache(CacheStorageFiles cacheStorageFiles) {\n    cacheStorageWriter = new CacheStorageWriter(cacheStorageFiles);\n    cacheStorageReader = new CacheStorageReader(cacheStorageFiles);\n  }\n\n  /**\n   * Saves image metadata (a manifest list and a list of manifest/container configuration pairs) for\n   * an image reference.\n   *\n   * @param imageReference the image reference to save the metadata for\n   * @param metadata the image metadata\n   * @throws IOException if an I/O exception occurs\n   */\n  public void writeMetadata(ImageReference imageReference, ImageMetadataTemplate metadata)\n      throws IOException {\n    cacheStorageWriter.writeMetadata(imageReference, metadata);\n  }\n\n  /**\n   * Saves a schema 2 manifest for an image reference. This is a simple wrapper around {@link\n   * #writeMetadata(ImageReference, ImageMetadataTemplate)} to save a single manifest without a\n   * manifest list.\n   *\n   * @param imageReference the image reference to save the manifest for\n   * @param manifest the V2.2 or OCI manifest\n   * @param containerConfiguration the container configuration\n   * @throws IOException if an I/O exception occurs\n   */\n  public void writeMetadata(\n      ImageReference imageReference,\n      BuildableManifestTemplate manifest,\n      ContainerConfigurationTemplate containerConfiguration)\n      throws IOException {\n    List<ManifestAndConfigTemplate> singleton =\n        Collections.singletonList(new ManifestAndConfigTemplate(manifest, containerConfiguration));\n    cacheStorageWriter.writeMetadata(imageReference, new ImageMetadataTemplate(null, singleton));\n  }\n\n  /**\n   * Saves a V2.1 image manifest. This is a simple wrapper around {@link\n   * #writeMetadata(ImageReference, ImageMetadataTemplate)} to save a single manifest without a\n   * manifest list.\n   *\n   * @param imageReference the image reference to save the manifest for\n   * @param manifestTemplate the V2.1 manifest\n   * @throws IOException if an I/O exception occurs\n   */\n  public void writeMetadata(ImageReference imageReference, V21ManifestTemplate manifestTemplate)\n      throws IOException {\n    List<ManifestAndConfigTemplate> singleton =\n        Collections.singletonList(new ManifestAndConfigTemplate(manifestTemplate, null));\n    cacheStorageWriter.writeMetadata(imageReference, new ImageMetadataTemplate(null, singleton));\n  }\n\n  /**\n   * Saves a cache entry with a compressed layer {@link Blob}. Use {@link\n   * #writeUncompressedLayer(Blob, ImmutableList)} to save a cache entry with an uncompressed layer\n   * {@link Blob} and include a selector.\n   *\n   * @param compressedLayerBlob the compressed layer {@link Blob}\n   * @return the {@link CachedLayer} for the written layer\n   * @throws IOException if an I/O exception occurs\n   */\n  public CachedLayer writeCompressedLayer(Blob compressedLayerBlob) throws IOException {\n    return cacheStorageWriter.writeCompressed(compressedLayerBlob);\n  }\n\n  /**\n   * Saves a cache entry with an uncompressed layer {@link Blob} and an additional selector digest.\n   * Use {@link #writeCompressedLayer(Blob)} to save a compressed layer {@link Blob}.\n   *\n   * @param uncompressedLayerBlob the layer {@link Blob}\n   * @param layerEntries the layer entries that make up the layer\n   * @return the {@link CachedLayer} for the written layer\n   * @throws IOException if an I/O exception occurs\n   */\n  public CachedLayer writeUncompressedLayer(\n      Blob uncompressedLayerBlob, ImmutableList<FileEntry> layerEntries) throws IOException {\n    return cacheStorageWriter.writeUncompressed(\n        uncompressedLayerBlob, LayerEntriesSelector.generateSelector(layerEntries));\n  }\n\n  /**\n   * Caches a layer that was extracted from a local base image, and names the file using the\n   * provided diff id.\n   *\n   * @param diffId the diff id\n   * @param compressedBlob the compressed layer blob\n   * @return the {@link CachedLayer} for the written layer\n   * @throws IOException if an I/O exception occurs\n   */\n  public CachedLayer writeTarLayer(DescriptorDigest diffId, Blob compressedBlob)\n      throws IOException {\n    return cacheStorageWriter.writeTarLayer(diffId, compressedBlob);\n  }\n\n  /**\n   * Writes a container configuration to {@code (cache directory)/local/config/(image id)}. An image\n   * ID is a SHA hash of a container configuration JSON. The value is also shown as IMAGE ID in\n   * {@code docker images}.\n   *\n   * <p>Note: the {@code imageId} to the {@code containerConfiguration} is a one-way relationship;\n   * there is no guarantee that {@code containerConfiguration}'s SHA will be {@code imageId}, since\n   * the original container configuration is being rewritten here rather than being moved.\n   *\n   * @param imageId the ID of the image to store the container configuration for\n   * @param containerConfiguration the container configuration\n   * @throws IOException if an I/O exception occurs\n   */\n  public void writeLocalConfig(\n      DescriptorDigest imageId, ContainerConfigurationTemplate containerConfiguration)\n      throws IOException {\n    cacheStorageWriter.writeLocalConfig(imageId, containerConfiguration);\n  }\n\n  /**\n   * Retrieves the cached image metadata (a manifest list and a list of manifest/container\n   * configuration pairs) for an image reference.\n   *\n   * @param imageReference the image reference\n   * @return the image metadata for the image reference, if found\n   * @throws IOException if an I/O exception occurs\n   * @throws CacheCorruptedException if the cache is corrupted\n   */\n  public Optional<ImageMetadataTemplate> retrieveMetadata(ImageReference imageReference)\n      throws IOException, CacheCorruptedException {\n    return cacheStorageReader.retrieveMetadata(imageReference);\n  }\n\n  /**\n   * Returns {@code true} if all image layers described in a manifest exist in the cache.\n   *\n   * @param manifest the image manifest\n   * @return a boolean\n   */\n  public boolean areAllLayersCached(ManifestTemplate manifest) {\n    return cacheStorageReader.areAllLayersCached(manifest);\n  }\n\n  /**\n   * Retrieves the {@link CachedLayer} that was built from the {@code layerEntries}.\n   *\n   * @param layerEntries the layer entries to match against\n   * @return a {@link CachedLayer} that was built from {@code layerEntries}, if found\n   * @throws IOException if an I/O exception occurs\n   * @throws CacheCorruptedException if the cache is corrupted\n   */\n  public Optional<CachedLayer> retrieve(ImmutableList<FileEntry> layerEntries)\n      throws IOException, CacheCorruptedException {\n    Optional<DescriptorDigest> optionalSelectedLayerDigest =\n        cacheStorageReader.select(LayerEntriesSelector.generateSelector(layerEntries));\n    if (!optionalSelectedLayerDigest.isPresent()) {\n      return Optional.empty();\n    }\n\n    return cacheStorageReader.retrieve(optionalSelectedLayerDigest.get());\n  }\n\n  /**\n   * Retrieves the {@link CachedLayer} for the layer with digest {@code layerDigest}.\n   *\n   * @param layerDigest the layer digest\n   * @return the {@link CachedLayer} referenced by the layer digest, if found\n   * @throws CacheCorruptedException if the cache was found to be corrupted\n   * @throws IOException if an I/O exception occurs\n   */\n  public Optional<CachedLayer> retrieve(DescriptorDigest layerDigest)\n      throws IOException, CacheCorruptedException {\n    return cacheStorageReader.retrieve(layerDigest);\n  }\n\n  /**\n   * Retrieves a {@link CachedLayer} for a local base image layer with the given diff id.\n   *\n   * @param diffId the diff id\n   * @return the {@link CachedLayer} with the given diff id\n   * @throws CacheCorruptedException if the cache was found to be corrupted\n   * @throws IOException if an I/O exception occurs\n   */\n  public Optional<CachedLayer> retrieveTarLayer(DescriptorDigest diffId)\n      throws IOException, CacheCorruptedException {\n    return cacheStorageReader.retrieveTarLayer(diffId);\n  }\n\n  /**\n   * Retrieves the {@link ContainerConfigurationTemplate} for the image saved from the given image\n   * ID. An image ID is a SHA hash of a container configuration JSON. The value is also shown as\n   * IMAGE ID in {@code docker images}.\n   *\n   * <p>Note: the {@code imageId} is only used to find the {@code containerConfiguration}, and is\n   * not necessarily the actual SHA of {@code containerConfiguration}. There is no guarantee that\n   * {@code containerConfiguration}'s SHA will be {@code imageId}, since the saved container\n   * configuration is not a direct copy of the base image's original configuration.\n   *\n   * @param imageId the image ID\n   * @return the {@link ContainerConfigurationTemplate} referenced by the image ID, if found\n   * @throws IOException if an I/O exception occurs\n   */\n  public Optional<ContainerConfigurationTemplate> retrieveLocalConfig(DescriptorDigest imageId)\n      throws IOException {\n    return cacheStorageReader.retrieveLocalConfig(imageId);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheCorruptedException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport java.nio.file.Path;\n\n/** Thrown if the the cache was found to be corrupted. */\npublic class CacheCorruptedException extends Exception {\n\n  CacheCorruptedException(Path cacheDirectory, String message, Throwable cause) {\n    super(\n        message\n            + \". You may need to clear the cache by deleting the '\"\n            + cacheDirectory\n            + \"' directory\",\n        cause);\n  }\n\n  CacheCorruptedException(Path cacheDirectory, String message) {\n    super(\n        message\n            + \". You may need to clear the cache by deleting the '\"\n            + cacheDirectory\n            + \"' directory\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageFiles.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.common.base.Splitter;\nimport java.nio.file.Path;\nimport java.security.DigestException;\n\n/** Resolves the files used in the default cache storage engine. */\nclass CacheStorageFiles {\n\n  private static final String LAYERS_DIRECTORY = \"layers\";\n  private static final String LOCAL_DIRECTORY = \"local\";\n  private static final String IMAGES_DIRECTORY = \"images\";\n  private static final String SELECTORS_DIRECTORY = \"selectors\";\n  private static final String TEMPORARY_DIRECTORY = \"tmp\";\n  private static final String TEMPORARY_LAYER_FILE_NAME = \".tmp.layer\";\n\n  /**\n   * Returns whether or not {@code file} is a layer contents file.\n   *\n   * @param file the file to check\n   * @return {@code true} if {@code file} is a layer contents file; {@code false} otherwise\n   */\n  static boolean isLayerFile(Path file) {\n    return file.getFileName().toString().length() == DescriptorDigest.HASH_LENGTH;\n  }\n\n  private final Path cacheDirectory;\n\n  CacheStorageFiles(Path cacheDirectory) {\n    this.cacheDirectory = cacheDirectory;\n  }\n\n  /**\n   * Gets the diff ID portion of the layer filename.\n   *\n   * @param layerFile the layer file to parse for the diff ID\n   * @return the diff ID portion of the layer file filename\n   * @throws CacheCorruptedException if no valid diff ID could be parsed\n   */\n  DescriptorDigest getDigestFromFilename(Path layerFile) throws CacheCorruptedException {\n    try {\n      String hash = layerFile.getFileName().toString();\n      return DescriptorDigest.fromHash(hash);\n\n    } catch (DigestException | IndexOutOfBoundsException ex) {\n      throw new CacheCorruptedException(\n          cacheDirectory, \"Layer file did not include valid hash: \" + layerFile, ex);\n    }\n  }\n\n  /**\n   * Gets the cache directory.\n   *\n   * @return the cache directory\n   */\n  Path getCacheDirectory() {\n    return cacheDirectory;\n  }\n\n  /**\n   * Resolves the layer contents file.\n   *\n   * @param layerDigest the layer digest\n   * @param layerDiffId the layer diff Id\n   * @return the layer contents file\n   */\n  Path getLayerFile(DescriptorDigest layerDigest, DescriptorDigest layerDiffId) {\n    return getLayerDirectory(layerDigest).resolve(getLayerFilename(layerDiffId));\n  }\n\n  /**\n   * Gets the filename for the layer file. The filename is in the form {@code <layer diff\n   * ID>.layer}.\n   *\n   * @param layerDiffId the layer's diff ID\n   * @return the layer filename\n   */\n  String getLayerFilename(DescriptorDigest layerDiffId) {\n    return layerDiffId.getHash();\n  }\n\n  /**\n   * Resolves a selector file.\n   *\n   * @param selector the selector digest\n   * @return the selector file\n   */\n  Path getSelectorFile(DescriptorDigest selector) {\n    return cacheDirectory.resolve(SELECTORS_DIRECTORY).resolve(selector.getHash());\n  }\n\n  /**\n   * Resolves the {@link #LAYERS_DIRECTORY} in the {@link #cacheDirectory}.\n   *\n   * @return the directory containing all the layer directories\n   */\n  Path getLayersDirectory() {\n    return cacheDirectory.resolve(LAYERS_DIRECTORY);\n  }\n\n  /**\n   * Gets the directory for the layer with digest {@code layerDigest}.\n   *\n   * @param layerDigest the digest of the layer\n   * @return the directory for that {@code layerDigest}\n   */\n  Path getLayerDirectory(DescriptorDigest layerDigest) {\n    return getLayersDirectory().resolve(layerDigest.getHash());\n  }\n\n  /**\n   * Resolves the {@link #LOCAL_DIRECTORY} in the {@link #cacheDirectory}.\n   *\n   * @return the directory containing local base image layers\n   */\n  Path getLocalDirectory() {\n    return cacheDirectory.resolve(LOCAL_DIRECTORY);\n  }\n\n  /**\n   * Gets the directory to store the image manifest and configuration.\n   *\n   * @return the directory for the image manifest and configuration\n   */\n  Path getImagesDirectory() {\n    return cacheDirectory.resolve(IMAGES_DIRECTORY);\n  }\n\n  /**\n   * Gets the directory corresponding to the given image reference.\n   *\n   * @param imageReference the image reference\n   * @return a path in the form of {@code\n   *     (jib-cache)/images/registry[!port]/repository!(tag|digest-type!digest)}\n   */\n  Path getImageDirectory(ImageReference imageReference) {\n    // Replace ':' and '@' with '!' to avoid directory-naming restrictions\n    String replacedReference =\n        imageReference.toStringWithQualifier().replace(':', '!').replace('@', '!');\n\n    // Split image reference on '/' to build directory structure\n    Iterable<String> directories = Splitter.on('/').split(replacedReference);\n    Path destination = getImagesDirectory();\n    for (String dir : directories) {\n      destination = destination.resolve(dir);\n    }\n    return destination;\n  }\n\n  /**\n   * Gets the directory to store temporary files.\n   *\n   * @return the directory for temporary files\n   */\n  Path getTemporaryDirectory() {\n    return cacheDirectory.resolve(TEMPORARY_DIRECTORY);\n  }\n\n  /**\n   * Resolves a file to use as a temporary file to write layer contents to.\n   *\n   * @param layerDirectory the directory in which to resolve the temporary layer file\n   * @return the temporary layer file\n   */\n  Path getTemporaryLayerFile(Path layerDirectory) {\n    Path temporaryLayerFile = layerDirectory.resolve(TEMPORARY_LAYER_FILE_NAME);\n    temporaryLayerFile.toFile().deleteOnExit();\n    return temporaryLayerFile;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageReader.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.filesystem.LockFile;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestException;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/** Reads from the default cache storage engine. */\nclass CacheStorageReader {\n\n  @VisibleForTesting\n  static void verifyImageMetadata(ImageMetadataTemplate metadata, Path metadataCacheDirectory)\n      throws CacheCorruptedException {\n    List<ManifestAndConfigTemplate> manifestsAndConfigs = metadata.getManifestsAndConfigs();\n    if (manifestsAndConfigs.isEmpty()) {\n      throw new CacheCorruptedException(metadataCacheDirectory, \"Manifest cache empty\");\n    }\n    if (manifestsAndConfigs.stream().anyMatch(entry -> entry.getManifest() == null)) {\n      throw new CacheCorruptedException(metadataCacheDirectory, \"Manifest(s) missing\");\n    }\n    if (metadata.getManifestList() == null && manifestsAndConfigs.size() != 1) {\n      throw new CacheCorruptedException(metadataCacheDirectory, \"Manifest list missing\");\n    }\n\n    ManifestTemplate firstManifest = manifestsAndConfigs.get(0).getManifest();\n    if (firstManifest instanceof V21ManifestTemplate) {\n      if (metadata.getManifestList() != null\n          || manifestsAndConfigs.stream().anyMatch(entry -> entry.getConfig() != null)) {\n        throw new CacheCorruptedException(metadataCacheDirectory, \"Schema 1 manifests corrupted\");\n      }\n    } else if (firstManifest instanceof BuildableManifestTemplate) {\n      if (manifestsAndConfigs.stream().anyMatch(entry -> entry.getConfig() == null)) {\n        throw new CacheCorruptedException(metadataCacheDirectory, \"Schema 2 manifests corrupted\");\n      }\n      if (metadata.getManifestList() != null\n          && manifestsAndConfigs.stream().anyMatch(entry -> entry.getManifestDigest() == null)) {\n        throw new CacheCorruptedException(metadataCacheDirectory, \"Schema 2 manifests corrupted\");\n      }\n    } else {\n      throw new CacheCorruptedException(\n          metadataCacheDirectory, \"Unknown manifest type: \" + firstManifest);\n    }\n  }\n\n  private final CacheStorageFiles cacheStorageFiles;\n\n  CacheStorageReader(CacheStorageFiles cacheStorageFiles) {\n    this.cacheStorageFiles = cacheStorageFiles;\n  }\n\n  /**\n   * Returns {@code true} if all image layers described in a manifest have a corresponding file\n   * entry in the cache.\n   *\n   * @param manifest the image manifest\n   * @return a boolean\n   */\n  boolean areAllLayersCached(ManifestTemplate manifest) {\n\n    List<DescriptorDigest> layerDigests;\n\n    if (manifest instanceof V21ManifestTemplate) {\n      layerDigests = ((V21ManifestTemplate) manifest).getLayerDigests();\n    } else if (manifest instanceof BuildableManifestTemplate) {\n      layerDigests =\n          ((BuildableManifestTemplate) manifest)\n              .getLayers().stream()\n                  .map(BuildableManifestTemplate.ContentDescriptorTemplate::getDigest)\n                  .collect(Collectors.toList());\n    } else {\n      throw new IllegalArgumentException(\"Unknown manifest type: \" + manifest);\n    }\n\n    for (DescriptorDigest layerDigest : layerDigests) {\n      Path layerDirectory = cacheStorageFiles.getLayerDirectory(layerDigest);\n      if (!Files.exists(layerDirectory)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Retrieves the cached image metadata (a manifest list and a list of manifest/container\n   * configuration pairs) for an image reference.\n   *\n   * @param imageReference the image reference\n   * @return the image metadata for the image reference, if found\n   * @throws IOException if an I/O exception occurs\n   * @throws CacheCorruptedException if the cache is corrupted\n   */\n  Optional<ImageMetadataTemplate> retrieveMetadata(ImageReference imageReference)\n      throws IOException, CacheCorruptedException {\n    Path imageDirectory = cacheStorageFiles.getImageDirectory(imageReference);\n    Path metadataPath = imageDirectory.resolve(\"manifests_configs.json\");\n    if (!Files.exists(metadataPath)) {\n      return Optional.empty();\n    }\n\n    ImageMetadataTemplate metadata;\n    try (LockFile ignored = LockFile.lock(imageDirectory.resolve(\"lock\"))) {\n      metadata = JsonTemplateMapper.readJsonFromFile(metadataPath, ImageMetadataTemplate.class);\n    }\n    verifyImageMetadata(metadata, imageDirectory);\n    return Optional.of(metadata);\n  }\n\n  /**\n   * Retrieves the {@link CachedLayer} for the layer with digest {@code layerDigest}.\n   *\n   * @param layerDigest the layer digest\n   * @return the {@link CachedLayer} referenced by the layer digest, if found\n   * @throws CacheCorruptedException if the cache was found to be corrupted\n   * @throws IOException if an I/O exception occurs\n   */\n  Optional<CachedLayer> retrieve(DescriptorDigest layerDigest)\n      throws IOException, CacheCorruptedException {\n    Path layerDirectory = cacheStorageFiles.getLayerDirectory(layerDigest);\n    if (!Files.exists(layerDirectory)) {\n      return Optional.empty();\n    }\n\n    try (Stream<Path> files = Files.list(layerDirectory)) {\n      List<Path> layerFiles =\n          files.filter(CacheStorageFiles::isLayerFile).collect(Collectors.toList());\n      if (layerFiles.size() != 1) {\n        throw new CacheCorruptedException(\n            cacheStorageFiles.getCacheDirectory(),\n            \"No or multiple layer files found for layer hash \"\n                + layerDigest.getHash()\n                + \" in directory: \"\n                + layerDirectory);\n      }\n\n      Path layerFile = layerFiles.get(0);\n      return Optional.of(\n          CachedLayer.builder()\n              .setLayerDigest(layerDigest)\n              .setLayerSize(Files.size(layerFile))\n              .setLayerBlob(Blobs.from(layerFile))\n              .setLayerDiffId(cacheStorageFiles.getDigestFromFilename(layerFile))\n              .build());\n    }\n  }\n\n  /**\n   * Retrieves the {@link CachedLayer} for the local base image layer with the given diff ID.\n   *\n   * @param diffId the diff ID\n   * @return the {@link CachedLayer} referenced by the diff ID, if found\n   * @throws CacheCorruptedException if the cache was found to be corrupted\n   * @throws IOException if an I/O exception occurs\n   */\n  Optional<CachedLayer> retrieveTarLayer(DescriptorDigest diffId)\n      throws IOException, CacheCorruptedException {\n    Path layerDirectory = cacheStorageFiles.getLocalDirectory().resolve(diffId.getHash());\n    if (!Files.exists(layerDirectory)) {\n      return Optional.empty();\n    }\n\n    try (Stream<Path> files = Files.list(layerDirectory)) {\n      List<Path> layerFiles =\n          files.filter(CacheStorageFiles::isLayerFile).collect(Collectors.toList());\n      if (layerFiles.size() != 1) {\n        throw new CacheCorruptedException(\n            cacheStorageFiles.getCacheDirectory(),\n            \"No or multiple layer files found for layer hash \"\n                + diffId.getHash()\n                + \" in directory: \"\n                + layerDirectory);\n      }\n\n      Path layerFile = layerFiles.get(0);\n      return Optional.of(\n          CachedLayer.builder()\n              .setLayerDigest(cacheStorageFiles.getDigestFromFilename(layerFile))\n              .setLayerSize(Files.size(layerFile))\n              .setLayerBlob(Blobs.from(layerFile))\n              .setLayerDiffId(diffId)\n              .build());\n    }\n  }\n\n  /**\n   * Retrieves the {@link ContainerConfigurationTemplate} for the image with the given image ID.\n   *\n   * @param imageId the image ID\n   * @return the {@link ContainerConfigurationTemplate} referenced by the image ID, if found\n   * @throws IOException if an I/O exception occurs\n   */\n  Optional<ContainerConfigurationTemplate> retrieveLocalConfig(DescriptorDigest imageId)\n      throws IOException {\n    Path configPath =\n        cacheStorageFiles.getLocalDirectory().resolve(\"config\").resolve(imageId.getHash());\n    if (!Files.exists(configPath)) {\n      return Optional.empty();\n    }\n\n    ContainerConfigurationTemplate config =\n        JsonTemplateMapper.readJsonFromFile(configPath, ContainerConfigurationTemplate.class);\n    return Optional.of(config);\n  }\n\n  /**\n   * Retrieves the layer digest selected by the {@code selector}.\n   *\n   * @param selector the selector\n   * @return the layer digest {@code selector} selects, if found\n   * @throws CacheCorruptedException if the selector file contents was not a valid layer digest\n   * @throws IOException if an I/O exception occurs\n   */\n  Optional<DescriptorDigest> select(DescriptorDigest selector)\n      throws CacheCorruptedException, IOException {\n    Path selectorFile = cacheStorageFiles.getSelectorFile(selector);\n    if (!Files.exists(selectorFile)) {\n      return Optional.empty();\n    }\n\n    String selectorFileContents =\n        new String(Files.readAllBytes(selectorFile), StandardCharsets.UTF_8);\n    try {\n      return Optional.of(DescriptorDigest.fromHash(selectorFileContents));\n\n    } catch (DigestException ex) {\n      throw new CacheCorruptedException(\n          cacheStorageFiles.getCacheDirectory(),\n          \"Expected valid layer digest as contents of selector file `\"\n              + selectorFile\n              + \"` for selector `\"\n              + selector.getHash()\n              + \"`, but got: \"\n              + selectorFileContents);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageWriter.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.cache.Retry.Action;\nimport com.google.cloud.tools.jib.filesystem.LockFile;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.hash.CountingDigestOutputStream;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.AtomicMoveNotSupportedException;\nimport java.nio.file.FileSystemException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Predicate;\nimport java.util.zip.GZIPOutputStream;\nimport javax.annotation.Nullable;\nimport org.apache.commons.compress.compressors.CompressorException;\nimport org.apache.commons.compress.compressors.CompressorStreamFactory;\n\n/** Writes to the default cache storage engine. */\nclass CacheStorageWriter {\n\n  /** Holds information about a layer that was written. */\n  private static class WrittenLayer {\n\n    private final DescriptorDigest layerDigest;\n    private final DescriptorDigest layerDiffId;\n    private final long layerSize;\n\n    private WrittenLayer(\n        DescriptorDigest layerDigest, DescriptorDigest layerDiffId, long layerSize) {\n      this.layerDigest = layerDigest;\n      this.layerDiffId = layerDiffId;\n      this.layerSize = layerSize;\n    }\n  }\n\n  private static void verifyImageMetadata(ImageMetadataTemplate metadata) {\n    Predicate<ManifestAndConfigTemplate> isManifestNull = pair -> pair.getManifest() == null;\n    Predicate<ManifestAndConfigTemplate> isConfigNull = pair -> pair.getConfig() == null;\n    Predicate<ManifestAndConfigTemplate> isDigestNull = pair -> pair.getManifestDigest() == null;\n\n    List<ManifestAndConfigTemplate> manifestsAndConfigs = metadata.getManifestsAndConfigs();\n    Preconditions.checkArgument(!manifestsAndConfigs.isEmpty(), \"no manifests given\");\n    Preconditions.checkArgument(\n        manifestsAndConfigs.stream().noneMatch(isManifestNull), \"null manifest(s)\");\n    Preconditions.checkArgument(\n        metadata.getManifestList() != null || manifestsAndConfigs.size() == 1,\n        \"manifest list missing while multiple manifests given\");\n\n    ManifestTemplate firstManifest = manifestsAndConfigs.get(0).getManifest();\n    if (firstManifest instanceof V21ManifestTemplate) {\n      Preconditions.checkArgument(\n          metadata.getManifestList() == null, \"manifest list given for schema 1\");\n      Preconditions.checkArgument(\n          isConfigNull.test(manifestsAndConfigs.get(0)), \"container config given for schema 1\");\n    } else if (firstManifest instanceof BuildableManifestTemplate) {\n      Preconditions.checkArgument(\n          manifestsAndConfigs.stream().noneMatch(isConfigNull), \"null container config(s)\");\n      if (metadata.getManifestList() != null) {\n        Preconditions.checkArgument(\n            manifestsAndConfigs.stream().noneMatch(isDigestNull), \"null manifest digest(s)\");\n      }\n    } else {\n      throw new IllegalArgumentException(\"Unknown manifest type: \" + firstManifest);\n    }\n  }\n\n  /**\n   * Attempts to move {@code source} to {@code destination}. If {@code destination} already exists,\n   * this does nothing. Attempts an atomic move first, and falls back to non-atomic if the\n   * filesystem does not support atomic moves.\n   *\n   * @param source the source path\n   * @param destination the destination path\n   * @throws IOException if an I/O exception occurs\n   */\n  @VisibleForTesting\n  static void moveIfDoesNotExist(Path source, Path destination) throws IOException {\n    String errorMessage =\n        String.format(\n            \"unable to move: %s to %s; such failures are often caused by interference from \"\n                + \"antivirus (https://github.com/GoogleContainerTools/jib/issues/3127#issuecomment-796838294), \"\n                + \"or rarely if the operation is not supported by the file system (for example: \"\n                + \"special non-local file system)\",\n            source, destination);\n\n    try {\n      Action<IOException> rename =\n          () -> {\n            if (Files.exists(destination)) {\n              // If the file already exists, we skip renaming and use the existing file.\n              // This happens, e.g., if a new layer happens to have the same content as a\n              // previously-cached layer or the same layer is being cached concurrently.\n              return true;\n            }\n            Files.move(source, destination);\n            return Files.exists(destination);\n          };\n      // Some Windows users report java.nio.file.AccessDeniedException that we suspect is caused\n      // by anti-virus programs, like Windows Defender, that open new files for scanning.\n      // Retry the rename up to 10 times, with 15ms pause between each retry.\n      if (!Retry.action(rename)\n          .maximumRetries(10)\n          .retryOnException(ex -> ex instanceof FileSystemException)\n          .sleep(15, TimeUnit.MILLISECONDS)\n          .run()) {\n        throw new IOException(errorMessage);\n      }\n\n    } catch (IOException ex) {\n      throw new IOException(errorMessage, ex);\n    }\n  }\n\n  /**\n   * Decompresses the file to obtain the diff ID.\n   *\n   * @param compressedFile the file containing the compressed contents\n   * @return the digest of the decompressed file\n   * @throws IOException if an I/O exception occurs\n   */\n  private static DescriptorDigest getDiffIdByDecompressingFile(Path compressedFile)\n      throws IOException {\n    try (InputStream in =\n        new CompressorStreamFactory(true)\n            .createCompressorInputStream(\n                new BufferedInputStream(Files.newInputStream(compressedFile)))) {\n      return Digests.computeDigest(in).getDigest();\n    } catch (CompressorException e) {\n      throw new IOException(e);\n    }\n  }\n\n  /**\n   * Writes a json template to the destination path by writing to a temporary file then moving the\n   * file.\n   *\n   * @param jsonTemplate the json template\n   * @param destination the destination path\n   * @throws IOException if an I/O exception occurs\n   */\n  private static void writeJsonTemplate(JsonTemplate jsonTemplate, Path destination)\n      throws IOException {\n    Path temporaryFile = Files.createTempFile(destination.getParent(), null, null);\n    temporaryFile.toFile().deleteOnExit();\n    try (OutputStream outputStream = Files.newOutputStream(temporaryFile)) {\n      JsonTemplateMapper.writeTo(jsonTemplate, outputStream);\n    }\n\n    // Attempts an atomic move first, and falls back to non-atomic if the file system does not\n    // support atomic moves.\n    try {\n      Files.move(\n          temporaryFile,\n          destination,\n          StandardCopyOption.ATOMIC_MOVE,\n          StandardCopyOption.REPLACE_EXISTING);\n\n    } catch (AtomicMoveNotSupportedException ignored) {\n      Files.move(temporaryFile, destination, StandardCopyOption.REPLACE_EXISTING);\n    }\n  }\n\n  private final CacheStorageFiles cacheStorageFiles;\n\n  CacheStorageWriter(CacheStorageFiles cacheStorageFiles) {\n    this.cacheStorageFiles = cacheStorageFiles;\n  }\n\n  /**\n   * Writes a compressed layer {@link Blob}.\n   *\n   * <p>The {@code compressedLayerBlob} is written to the layer directory under the layers directory\n   * corresponding to the layer blob.\n   *\n   * @param compressedLayerBlob the compressed layer {@link Blob} to write out\n   * @return the {@link CachedLayer} representing the written entry\n   * @throws IOException if an I/O exception occurs\n   */\n  CachedLayer writeCompressed(Blob compressedLayerBlob) throws IOException {\n    // Creates the layers directory if it doesn't exist.\n    Files.createDirectories(cacheStorageFiles.getLayersDirectory());\n\n    // Creates the temporary directory.\n    Files.createDirectories(cacheStorageFiles.getTemporaryDirectory());\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      Path temporaryLayerDirectory =\n          tempDirectoryProvider.newDirectory(cacheStorageFiles.getTemporaryDirectory());\n\n      // Writes the layer file to the temporary directory.\n      WrittenLayer writtenLayer =\n          writeCompressedLayerBlobToDirectory(compressedLayerBlob, temporaryLayerDirectory);\n\n      // Moves the temporary directory to the final location.\n      moveIfDoesNotExist(\n          temporaryLayerDirectory, cacheStorageFiles.getLayerDirectory(writtenLayer.layerDigest));\n\n      // Updates cachedLayer with the blob information.\n      Path layerFile =\n          cacheStorageFiles.getLayerFile(writtenLayer.layerDigest, writtenLayer.layerDiffId);\n      return CachedLayer.builder()\n          .setLayerDigest(writtenLayer.layerDigest)\n          .setLayerDiffId(writtenLayer.layerDiffId)\n          .setLayerSize(writtenLayer.layerSize)\n          .setLayerBlob(Blobs.from(layerFile))\n          .build();\n    }\n  }\n\n  /**\n   * Writes an uncompressed {@link Blob} out to the cache directory.\n   *\n   * <p>Cache is written out in the form:\n   *\n   * <ul>\n   *   <li>The {@code uncompressedLayerBlob} is written to the layer directory under the layers\n   *       directory corresponding to the layer blob.\n   *   <li>The {@code selector} is written to the selector file under the selectors directory.\n   * </ul>\n   *\n   * @param uncompressedLayerBlob the {@link Blob} containing the uncompressed layer contents to\n   *     write out\n   * @param selector the optional selector digest to also reference this layer data. A selector\n   *     digest may be a secondary identifier for a layer that is distinct from the default layer\n   *     digest.\n   * @return the {@link CachedLayer} representing the written entry\n   * @throws IOException if an I/O exception occurs\n   */\n  CachedLayer writeUncompressed(Blob uncompressedLayerBlob, @Nullable DescriptorDigest selector)\n      throws IOException {\n    // Creates the layers directory if it doesn't exist.\n    Files.createDirectories(cacheStorageFiles.getLayersDirectory());\n\n    // Creates the temporary directory. The temporary directory must be in the same FileStore as the\n    // final location for Files.move to work.\n    Files.createDirectories(cacheStorageFiles.getTemporaryDirectory());\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      Path temporaryLayerDirectory =\n          tempDirectoryProvider.newDirectory(cacheStorageFiles.getTemporaryDirectory());\n\n      // Writes the layer file to the temporary directory.\n      WrittenLayer writtenLayer =\n          writeUncompressedLayerBlobToDirectory(uncompressedLayerBlob, temporaryLayerDirectory);\n\n      // Moves the temporary directory to the final location.\n      moveIfDoesNotExist(\n          temporaryLayerDirectory, cacheStorageFiles.getLayerDirectory(writtenLayer.layerDigest));\n\n      // Updates cachedLayer with the blob information.\n      Path layerFile =\n          cacheStorageFiles.getLayerFile(writtenLayer.layerDigest, writtenLayer.layerDiffId);\n      CachedLayer.Builder cachedLayerBuilder =\n          CachedLayer.builder()\n              .setLayerDigest(writtenLayer.layerDigest)\n              .setLayerDiffId(writtenLayer.layerDiffId)\n              .setLayerSize(writtenLayer.layerSize)\n              .setLayerBlob(Blobs.from(layerFile));\n\n      // Write the selector file.\n      if (selector != null) {\n        writeSelector(selector, writtenLayer.layerDigest);\n      }\n\n      return cachedLayerBuilder.build();\n    }\n  }\n\n  /**\n   * Saves a local base image layer.\n   *\n   * @param diffId the layer blob's diff ID\n   * @param compressedBlob the blob to save\n   * @throws IOException if an I/O exception occurs\n   */\n  CachedLayer writeTarLayer(DescriptorDigest diffId, Blob compressedBlob) throws IOException {\n    Files.createDirectories(cacheStorageFiles.getLocalDirectory());\n    Files.createDirectories(cacheStorageFiles.getTemporaryDirectory());\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      Path temporaryLayerDirectory =\n          tempDirectoryProvider.newDirectory(cacheStorageFiles.getTemporaryDirectory());\n      Path temporaryLayerFile = cacheStorageFiles.getTemporaryLayerFile(temporaryLayerDirectory);\n\n      BlobDescriptor layerBlobDescriptor;\n      try (OutputStream fileOutputStream =\n          new BufferedOutputStream(Files.newOutputStream(temporaryLayerFile))) {\n        layerBlobDescriptor = compressedBlob.writeTo(fileOutputStream);\n      }\n\n      // Renames the temporary layer file to its digest\n      // (temp/temp -> temp/<digest>)\n      String fileName = layerBlobDescriptor.getDigest().getHash();\n      Path digestLayerFile = temporaryLayerDirectory.resolve(fileName);\n      moveIfDoesNotExist(temporaryLayerFile, digestLayerFile);\n\n      // Moves the temporary directory to directory named with diff ID\n      // (temp/<digest> -> <diffID>/<digest>)\n      Path destination = cacheStorageFiles.getLocalDirectory().resolve(diffId.getHash());\n      moveIfDoesNotExist(temporaryLayerDirectory, destination);\n\n      return CachedLayer.builder()\n          .setLayerDigest(layerBlobDescriptor.getDigest())\n          .setLayerDiffId(diffId)\n          .setLayerSize(layerBlobDescriptor.getSize())\n          .setLayerBlob(Blobs.from(destination.resolve(fileName)))\n          .build();\n    }\n  }\n\n  /**\n   * Saves image metadata (a manifest list and a list of manifest/container configuration pairs) for\n   * an image reference.\n   *\n   * @param imageReference the image reference to store the metadata for\n   * @param metadata the image metadata\n   */\n  void writeMetadata(ImageReference imageReference, ImageMetadataTemplate metadata)\n      throws IOException {\n    verifyImageMetadata(metadata);\n    Path imageDirectory = cacheStorageFiles.getImageDirectory(imageReference);\n    Files.createDirectories(imageDirectory);\n\n    try (LockFile ignored = LockFile.lock(imageDirectory.resolve(\"lock\"))) {\n      writeJsonTemplate(metadata, imageDirectory.resolve(\"manifests_configs.json\"));\n    }\n  }\n\n  /**\n   * Writes a container configuration to {@code (cache directory)/local/config/(image id)}.\n   *\n   * @param imageId the ID of the image to store the container configuration for\n   * @param containerConfiguration the container configuration\n   * @throws IOException if an I/O exception occurs\n   */\n  void writeLocalConfig(\n      DescriptorDigest imageId, ContainerConfigurationTemplate containerConfiguration)\n      throws IOException {\n    Path configDirectory = cacheStorageFiles.getLocalDirectory().resolve(\"config\");\n    Files.createDirectories(configDirectory);\n    writeJsonTemplate(containerConfiguration, configDirectory.resolve(imageId.getHash()));\n  }\n\n  /**\n   * Writes a compressed {@code layerBlob} to the {@code layerDirectory}.\n   *\n   * @param compressedLayerBlob the compressed layer {@link Blob}\n   * @param layerDirectory the directory for the layer\n   * @return a {@link WrittenLayer} with the written layer information\n   * @throws IOException if an I/O exception occurs\n   */\n  private WrittenLayer writeCompressedLayerBlobToDirectory(\n      Blob compressedLayerBlob, Path layerDirectory) throws IOException {\n    // Writes the layer file to the temporary directory.\n    Path temporaryLayerFile = cacheStorageFiles.getTemporaryLayerFile(layerDirectory);\n\n    BlobDescriptor layerBlobDescriptor;\n    try (OutputStream fileOutputStream =\n        new BufferedOutputStream(Files.newOutputStream(temporaryLayerFile))) {\n      layerBlobDescriptor = compressedLayerBlob.writeTo(fileOutputStream);\n    }\n\n    // Gets the diff ID.\n    DescriptorDigest layerDiffId = getDiffIdByDecompressingFile(temporaryLayerFile);\n\n    // Renames the temporary layer file to the correct filename.\n    Path layerFile = layerDirectory.resolve(cacheStorageFiles.getLayerFilename(layerDiffId));\n    moveIfDoesNotExist(temporaryLayerFile, layerFile);\n\n    return new WrittenLayer(\n        layerBlobDescriptor.getDigest(), layerDiffId, layerBlobDescriptor.getSize());\n  }\n\n  /**\n   * Writes an uncompressed {@code layerBlob} to the {@code layerDirectory}.\n   *\n   * @param uncompressedLayerBlob the uncompressed layer {@link Blob}\n   * @param layerDirectory the directory for the layer\n   * @return a {@link WrittenLayer} with the written layer information\n   * @throws IOException if an I/O exception occurs\n   */\n  private WrittenLayer writeUncompressedLayerBlobToDirectory(\n      Blob uncompressedLayerBlob, Path layerDirectory) throws IOException {\n    Path temporaryLayerFile = cacheStorageFiles.getTemporaryLayerFile(layerDirectory);\n\n    try (CountingDigestOutputStream compressedDigestOutputStream =\n        new CountingDigestOutputStream(\n            new BufferedOutputStream(Files.newOutputStream(temporaryLayerFile)))) {\n      // Writes the layer with GZIP compression. The original bytes are captured as the layer's\n      // diff ID and the bytes outputted from the GZIP compression are captured as the layer's\n      // content descriptor.\n      GZIPOutputStream compressorStream = new GZIPOutputStream(compressedDigestOutputStream);\n      DescriptorDigest layerDiffId = uncompressedLayerBlob.writeTo(compressorStream).getDigest();\n\n      // The GZIPOutputStream must be closed in order to write out the remaining compressed data.\n      compressorStream.close();\n      BlobDescriptor blobDescriptor = compressedDigestOutputStream.computeDigest();\n      DescriptorDigest layerDigest = blobDescriptor.getDigest();\n      long layerSize = blobDescriptor.getSize();\n\n      // Renames the temporary layer file to the correct filename.\n      Path layerFile = layerDirectory.resolve(cacheStorageFiles.getLayerFilename(layerDiffId));\n      moveIfDoesNotExist(temporaryLayerFile, layerFile);\n\n      return new WrittenLayer(layerDigest, layerDiffId, layerSize);\n    }\n  }\n\n  /**\n   * Writes the {@code selector} to a file in the selectors directory, with contents {@code\n   * layerDigest}.\n   *\n   * @param selector the selector\n   * @param layerDigest the layer digest it selects\n   * @throws IOException if an I/O exception occurs\n   */\n  private void writeSelector(DescriptorDigest selector, DescriptorDigest layerDigest)\n      throws IOException {\n    Path selectorFile = cacheStorageFiles.getSelectorFile(selector);\n\n    // Creates the selectors directory if it doesn't exist.\n    Files.createDirectories(selectorFile.getParent());\n\n    // Writes the selector to a temporary file and then moves the file to the intended location.\n    Path temporarySelectorFile = Files.createTempFile(null, null);\n    temporarySelectorFile.toFile().deleteOnExit();\n    try (OutputStream fileOut = Files.newOutputStream(temporarySelectorFile)) {\n      fileOut.write(layerDigest.getHash().getBytes(StandardCharsets.UTF_8));\n    }\n\n    // Attempts an atomic move first, and falls back to non-atomic if the file system does not\n    // support atomic moves.\n    try {\n      Files.move(\n          temporarySelectorFile,\n          selectorFile,\n          StandardCopyOption.ATOMIC_MOVE,\n          StandardCopyOption.REPLACE_EXISTING);\n\n    } catch (AtomicMoveNotSupportedException ignored) {\n      Files.move(temporarySelectorFile, selectorFile, StandardCopyOption.REPLACE_EXISTING);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/CachedLayer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.common.base.Preconditions;\nimport javax.annotation.Nullable;\n\n/** A reference to an image layer that is in the Cache. */\npublic class CachedLayer implements Layer {\n\n  /** Builds a {@link CachedLayer}. */\n  public static class Builder {\n\n    @Nullable private DescriptorDigest layerDigest;\n    @Nullable private DescriptorDigest layerDiffId;\n    private long layerSize = -1;\n    @Nullable private Blob layerBlob;\n\n    private Builder() {}\n\n    public Builder setLayerDigest(DescriptorDigest layerDigest) {\n      this.layerDigest = layerDigest;\n      return this;\n    }\n\n    public Builder setLayerDiffId(DescriptorDigest layerDiffId) {\n      this.layerDiffId = layerDiffId;\n      return this;\n    }\n\n    public Builder setLayerSize(long layerSize) {\n      this.layerSize = layerSize;\n      return this;\n    }\n\n    public Builder setLayerBlob(Blob layerBlob) {\n      this.layerBlob = layerBlob;\n      return this;\n    }\n\n    boolean hasLayerBlob() {\n      return layerBlob != null;\n    }\n\n    /**\n     * Creates a CachedLayer instance.\n     *\n     * @return a new cached layer\n     */\n    public CachedLayer build() {\n      return new CachedLayer(\n          Preconditions.checkNotNull(layerDigest, \"layerDigest required\"),\n          Preconditions.checkNotNull(layerDiffId, \"layerDiffId required\"),\n          layerSize,\n          Preconditions.checkNotNull(layerBlob, \"layerBlob required\"));\n    }\n  }\n\n  /**\n   * Creates a new {@link Builder} for a {@link CachedLayer}.\n   *\n   * @return the new {@link Builder}\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private final DescriptorDigest layerDiffId;\n  private final BlobDescriptor blobDescriptor;\n  private final Blob layerBlob;\n\n  private CachedLayer(\n      DescriptorDigest layerDigest, DescriptorDigest layerDiffId, long layerSize, Blob layerBlob) {\n    this.layerDiffId = layerDiffId;\n    this.layerBlob = layerBlob;\n    this.blobDescriptor = new BlobDescriptor(layerSize, layerDigest);\n  }\n\n  public DescriptorDigest getDigest() {\n    return blobDescriptor.getDigest();\n  }\n\n  public long getSize() {\n    return blobDescriptor.getSize();\n  }\n\n  @Override\n  public DescriptorDigest getDiffId() {\n    return layerDiffId;\n  }\n\n  @Override\n  public Blob getBlob() {\n    return layerBlob;\n  }\n\n  @Override\n  public BlobDescriptor getBlobDescriptor() {\n    return blobDescriptor;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/LayerEntriesSelector.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * Generates a selector based on {@link FileEntry}s for a layer. Selectors are secondary references\n * for a cache entries.\n *\n * <p>The selector is the SHA256 hash of the list of layer entries serialized in the following form:\n *\n * <pre>{@code\n * [\n *   {\n *     \"sourceFile\": \"source/file/for/layer/entry/1\",\n *     \"extractionPath\": \"/extraction/path/for/layer/entry/1\"\n *     \"sourceModificationTime\": \"2018-10-03T15:48:32.416152Z\"\n *     \"targetModificationTime\": \"1970-01-01T00:00:01Z\",\n *     \"permissions\": \"777\",\n *     \"ownership\": \"0:0\"\n *   },\n *   {\n *     \"sourceFile\": \"source/file/for/layer/entry/2\",\n *     \"extractionPath\": \"/extraction/path/for/layer/entry/2\"\n *     \"sourceModificationTime\": \"2018-10-03T15:48:32.416152Z\"\n *     \"targetModificationTime\": \"1970-01-01T00:00:01Z\",\n *     \"permissions\": \"777\",\n *     \"ownership\": \"alice:1234\"\n *   }\n * ]\n * }</pre>\n */\nclass LayerEntriesSelector {\n\n  /** Serialized form of a {@link FileEntry}. */\n  @VisibleForTesting\n  static class LayerEntryTemplate implements JsonTemplate, Comparable<LayerEntryTemplate> {\n\n    private final String sourceFile;\n    private final String extractionPath;\n    private final Instant sourceModificationTime;\n    private final Instant targetModificationTime;\n    private final String permissions;\n    private final String ownership;\n\n    @VisibleForTesting\n    LayerEntryTemplate(FileEntry layerEntry) throws IOException {\n      sourceFile = layerEntry.getSourceFile().toAbsolutePath().toString();\n      extractionPath = layerEntry.getExtractionPath().toString();\n      sourceModificationTime = Files.getLastModifiedTime(layerEntry.getSourceFile()).toInstant();\n      targetModificationTime = layerEntry.getModificationTime();\n      permissions = layerEntry.getPermissions().toOctalString();\n      ownership = layerEntry.getOwnership();\n    }\n\n    @Override\n    public int compareTo(LayerEntryTemplate otherLayerEntryTemplate) {\n      int sourceFileComparison = sourceFile.compareTo(otherLayerEntryTemplate.sourceFile);\n      if (sourceFileComparison != 0) {\n        return sourceFileComparison;\n      }\n      int extractionPathComparison =\n          extractionPath.compareTo(otherLayerEntryTemplate.extractionPath);\n      if (extractionPathComparison != 0) {\n        return extractionPathComparison;\n      }\n      int sourceModificationTimeComparison =\n          sourceModificationTime.compareTo(otherLayerEntryTemplate.sourceModificationTime);\n      if (sourceModificationTimeComparison != 0) {\n        return sourceModificationTimeComparison;\n      }\n      int targetModificationTimeComparison =\n          targetModificationTime.compareTo(otherLayerEntryTemplate.targetModificationTime);\n      if (targetModificationTimeComparison != 0) {\n        return targetModificationTimeComparison;\n      }\n      int permissionsComparison = permissions.compareTo(otherLayerEntryTemplate.permissions);\n      if (permissionsComparison != 0) {\n        return permissionsComparison;\n      }\n      return ownership.compareTo(otherLayerEntryTemplate.ownership);\n    }\n\n    @Override\n    public boolean equals(Object other) {\n      if (this == other) {\n        return true;\n      }\n      if (!(other instanceof LayerEntryTemplate)) {\n        return false;\n      }\n      LayerEntryTemplate otherLayerEntryTemplate = (LayerEntryTemplate) other;\n      return sourceFile.equals(otherLayerEntryTemplate.sourceFile)\n          && extractionPath.equals(otherLayerEntryTemplate.extractionPath)\n          && sourceModificationTime.equals(otherLayerEntryTemplate.sourceModificationTime)\n          && targetModificationTime.equals(otherLayerEntryTemplate.targetModificationTime)\n          && permissions.equals(otherLayerEntryTemplate.permissions)\n          && ownership.equals(otherLayerEntryTemplate.ownership);\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(\n          sourceFile,\n          extractionPath,\n          sourceModificationTime,\n          targetModificationTime,\n          permissions,\n          ownership);\n    }\n  }\n\n  /**\n   * Converts a list of {@link FileEntry}s into a list of {@link LayerEntryTemplate}. The list is\n   * sorted by source file first, then extraction path (see {@link LayerEntryTemplate#compareTo}).\n   *\n   * @param layerEntries the list of {@link FileEntry} to convert\n   * @return list of {@link LayerEntryTemplate} after sorting\n   * @throws IOException if checking the file creation time of a layer entry fails\n   */\n  @VisibleForTesting\n  static List<LayerEntryTemplate> toSortedJsonTemplates(List<FileEntry> layerEntries)\n      throws IOException {\n    List<LayerEntryTemplate> jsonTemplates = new ArrayList<>();\n    for (FileEntry entry : layerEntries) {\n      jsonTemplates.add(new LayerEntryTemplate(entry));\n    }\n    Collections.sort(jsonTemplates);\n    return jsonTemplates;\n  }\n\n  /**\n   * Generates a selector for the list of {@link FileEntry}s. The selector is unique to each unique\n   * set of layer entries, regardless of order. TODO: Should we care about order?\n   *\n   * @param layerEntries the layer entries\n   * @return the selector\n   * @throws IOException if an I/O exception occurs\n   */\n  static DescriptorDigest generateSelector(ImmutableList<FileEntry> layerEntries)\n      throws IOException {\n    return Digests.computeJsonDigest(toSortedJsonTemplates(layerEntries));\n  }\n\n  private LayerEntriesSelector() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/cache/Retry.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.common.base.Preconditions;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Predicate;\n\n/**\n * Retries an action until it succeeds, or has retried too often and failed. By default the action\n * will be run up to 5 times. The action is deemed successful if it runs to completion without\n * throwing an exception, and returns true.\n *\n * <ul>\n *   <li>Exceptions are caught and, if deemed {@link #retryOnException(Predicate) retryable} then\n *       the action will be re-attempted. By default, any exception is considered retryable.\n *   <li>The retry instance can be configured to {@link #sleep(long, TimeUnit) sleep between\n *       retries}.\n *   <li>The maximum retry count {@link #maximumRetries(int) is configurable} (5 times by default).\n * </ul>\n *\n * @param <E> the class of exceptions that may be thrown\n */\npublic class Retry<E extends Exception> {\n\n  /** A runnable action that may throw an exception of type {@code E}. */\n  @FunctionalInterface\n  public interface Action<E extends Exception> {\n    /**\n     * Perform the action.\n     *\n     * @return {@code true} if the action was successful and {@code false} otherwise\n     * @throws E exception thrown during the action\n     */\n    boolean run() throws E;\n  }\n\n  /**\n   * Create a retryable action.\n   *\n   * @param action the action to be run\n   * @param <E> the class of exceptions that may be thrown\n   * @return the instance\n   */\n  public static <E extends Exception> Retry<E> action(Action<E> action) {\n    return new Retry<>(action);\n  }\n\n  private final Action<E> action;\n  private int maximumRetries = 5;\n  private Predicate<Exception> retryOnException = ignored -> true; // continue to retry\n  private long sleepMilliseconds = -1; // no sleep\n\n  private Retry(Action<E> action) {\n    this.action = action;\n  }\n\n  /**\n   * Configure the maximum number of retries.\n   *\n   * @param maximumRetries the number of retries, must be zero or more\n   * @return this Retry instance\n   */\n  public Retry<E> maximumRetries(int maximumRetries) {\n    Preconditions.checkArgument(maximumRetries > 0);\n    this.maximumRetries = maximumRetries;\n    return this;\n  }\n\n  /**\n   * Provide a predicate to determine if a thrown exception can be retried.\n   *\n   * @param retryOnException determine if provided exception is retryable.\n   * @return the instance for further configuration\n   */\n  public Retry<E> retryOnException(Predicate<Exception> retryOnException) {\n    this.retryOnException = retryOnException;\n    return this;\n  }\n\n  /**\n   * Set the sleep time between retries.\n   *\n   * @param duration the time to sleep\n   * @param unit the unit of time of duration\n   * @return the instance for further configuration\n   */\n  public Retry<E> sleep(long duration, TimeUnit unit) {\n    Preconditions.checkArgument(duration >= 0);\n    this.sleepMilliseconds = unit.convert(duration, TimeUnit.MILLISECONDS);\n    return this;\n  }\n\n  /**\n   * Run the action until it runs successfully, to a {@link #maximumRetries(int) maximum number of\n   * retries} (default: 5). If an exception occurs then the action will be retried providing {@link\n   * #retryOnException(Predicate) the exception is retryable}.\n   *\n   * @return true if the action was run successfully, or {@code false} if the action was unable to\n   *     complete\n   * @throws E exception thrown during the action\n   */\n  public boolean run() throws E {\n    for (int i = 0; i < maximumRetries; i++) {\n      try {\n        // sleep between attempts, but not on the first attempt\n        if (i > 0 && sleepMilliseconds >= 0) {\n          Thread.sleep(sleepMilliseconds);\n        }\n\n        // Do we need to continue?\n        if (action.run()) {\n          return true;\n        }\n\n      } catch (InterruptedException ex) {\n        // Restore the interrupted status\n        Thread.currentThread().interrupt();\n        return false;\n\n      } catch (Exception ex) {\n        // if this is the last iteration, no more retries\n        if (i + 1 == maximumRetries || !retryOnException.test(ex)) {\n          throw ex;\n        }\n      }\n    }\n    // we did not complete\n    return false;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/configuration/BuildContext.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Strings;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.ListMultimap;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.StringJoiner;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport javax.annotation.Nullable;\n\n/**\n * Build context for the builder process. Includes static build configuration options as well as\n * various services for execution (such as event dispatching, thread execution service, and HTTP\n * client). Informational instances (particularly configuration options such as {@link\n * ContainerConfiguration}, {@link ImageConfiguration}, and {@link FileEntriesLayer}) held in are\n * immutable.\n */\npublic class BuildContext implements Closeable {\n\n  /** The default target format of the container manifest. */\n  private static final Class<? extends BuildableManifestTemplate> DEFAULT_TARGET_FORMAT =\n      V22ManifestTemplate.class;\n\n  /** The default tool identifier. */\n  private static final String DEFAULT_TOOL_NAME = \"jib\";\n\n  /** Builds an immutable {@link BuildContext}. Instantiate with {@link #builder}. */\n  public static class Builder {\n\n    // All the parameters below are set to their default values.\n    @Nullable private ImageConfiguration baseImageConfiguration;\n    @Nullable private ImageConfiguration targetImageConfiguration;\n    private ImmutableSet<String> additionalTargetImageTags = ImmutableSet.of();\n    @Nullable private ContainerConfiguration containerConfiguration;\n    @Nullable private Path applicationLayersCacheDirectory;\n    @Nullable private Path baseImageLayersCacheDirectory;\n    private boolean allowInsecureRegistries = false;\n    private boolean offline = false;\n    private ImmutableList<FileEntriesLayer> layerConfigurations = ImmutableList.of();\n    private Class<? extends BuildableManifestTemplate> targetFormat = DEFAULT_TARGET_FORMAT;\n    private String toolName = DEFAULT_TOOL_NAME;\n    @Nullable private String toolVersion;\n    private EventHandlers eventHandlers = EventHandlers.NONE;\n    @Nullable private ExecutorService executorService;\n    private boolean alwaysCacheBaseImage = false;\n    private ImmutableListMultimap<String, String> registryMirrors = ImmutableListMultimap.of();\n    private boolean enablePlatformTags = false;\n\n    private Builder() {}\n\n    /**\n     * Sets the base image configuration.\n     *\n     * @param imageConfiguration the {@link ImageConfiguration} describing the base image\n     * @return this\n     */\n    public Builder setBaseImageConfiguration(ImageConfiguration imageConfiguration) {\n      baseImageConfiguration = imageConfiguration;\n      return this;\n    }\n\n    /**\n     * Sets the target image configuration.\n     *\n     * @param imageConfiguration the {@link ImageConfiguration} describing the target image\n     * @return this\n     */\n    public Builder setTargetImageConfiguration(ImageConfiguration imageConfiguration) {\n      targetImageConfiguration = imageConfiguration;\n      return this;\n    }\n\n    /**\n     * Sets the tags to tag the target image with (in addition to the tag in the target image\n     * configuration image reference set via {@link #setTargetImageConfiguration}).\n     *\n     * @param tags a set of tags\n     * @return this\n     */\n    public Builder setAdditionalTargetImageTags(Set<String> tags) {\n      additionalTargetImageTags = ImmutableSet.copyOf(tags);\n      return this;\n    }\n\n    /**\n     * Sets whether to automatically add architecture suffix to tags for platform-specific images\n     * when building multi-platform images. For example, when building amd64 and arm64 images for a\n     * given tag, the final tags will be {@code <tag>-amd64} and {@code <tag>-arm64}.\n     *\n     * @param enablePlatformTags whether to append architecture suffix to tags\n     * @return this\n     */\n    public Builder setEnablePlatformTags(boolean enablePlatformTags) {\n      this.enablePlatformTags = enablePlatformTags;\n      return this;\n    }\n\n    /**\n     * Sets configuration parameters for the container.\n     *\n     * @param containerConfiguration the {@link ContainerConfiguration}\n     * @return this\n     */\n    public Builder setContainerConfiguration(ContainerConfiguration containerConfiguration) {\n      this.containerConfiguration = containerConfiguration;\n      return this;\n    }\n\n    /**\n     * Sets the location of the cache for storing application layers.\n     *\n     * @param applicationLayersCacheDirectory the application layers cache directory\n     * @return this\n     */\n    public Builder setApplicationLayersCacheDirectory(Path applicationLayersCacheDirectory) {\n      this.applicationLayersCacheDirectory = applicationLayersCacheDirectory;\n      return this;\n    }\n\n    /**\n     * Sets the location of the cache for storing base image layers.\n     *\n     * @param baseImageLayersCacheDirectory the base image layers cache directory\n     * @return this\n     */\n    public Builder setBaseImageLayersCacheDirectory(Path baseImageLayersCacheDirectory) {\n      this.baseImageLayersCacheDirectory = baseImageLayersCacheDirectory;\n      return this;\n    }\n\n    /**\n     * Sets the target format of the container image.\n     *\n     * @param targetFormat the target format\n     * @return this\n     */\n    public Builder setTargetFormat(ImageFormat targetFormat) {\n      this.targetFormat =\n          targetFormat == ImageFormat.Docker\n              ? V22ManifestTemplate.class\n              : OciManifestTemplate.class;\n      return this;\n    }\n\n    /**\n     * Sets whether or not to allow communication over HTTP (as opposed to HTTPS).\n     *\n     * @param allowInsecureRegistries if {@code true}, insecure connections will be allowed\n     * @return this\n     */\n    public Builder setAllowInsecureRegistries(boolean allowInsecureRegistries) {\n      this.allowInsecureRegistries = allowInsecureRegistries;\n      return this;\n    }\n\n    /**\n     * Sets whether or not to perform the build in offline mode.\n     *\n     * @param offline if {@code true}, the build will run in offline mode\n     * @return this\n     */\n    public Builder setOffline(boolean offline) {\n      this.offline = offline;\n      return this;\n    }\n\n    /**\n     * Controls the optimization which skips downloading base image layers that exist in a target\n     * registry. If the user does not set this property then read as false.\n     *\n     * @param alwaysCacheBaseImage if {@code true}, base image layers are always pulled and cached.\n     *     If {@code false}, base image layers will not be pulled/cached if they already exist on\n     *     the target registry.\n     * @return this\n     */\n    public Builder setAlwaysCacheBaseImage(boolean alwaysCacheBaseImage) {\n      this.alwaysCacheBaseImage = alwaysCacheBaseImage;\n      return this;\n    }\n\n    /**\n     * Sets the layers to build.\n     *\n     * @param layerConfigurations the configurations for the layers\n     * @return this\n     */\n    public Builder setLayerConfigurations(List<FileEntriesLayer> layerConfigurations) {\n      this.layerConfigurations = ImmutableList.copyOf(layerConfigurations);\n      return this;\n    }\n\n    /**\n     * Sets the name of the tool that is executing the build.\n     *\n     * @param toolName the tool name\n     * @return this\n     */\n    public Builder setToolName(String toolName) {\n      this.toolName = toolName;\n      return this;\n    }\n\n    /**\n     * Sets the version of the tool that is executing the build.\n     *\n     * @param toolVersion the tool version\n     * @return this\n     */\n    public Builder setToolVersion(@Nullable String toolVersion) {\n      this.toolVersion = toolVersion;\n      return this;\n    }\n\n    /**\n     * Sets the {@link EventHandlers} to dispatch events with.\n     *\n     * @param eventHandlers the {@link EventHandlers}\n     * @return this\n     */\n    public Builder setEventHandlers(EventHandlers eventHandlers) {\n      this.eventHandlers = eventHandlers;\n      return this;\n    }\n\n    /**\n     * Sets the {@link ExecutorService} Jib executes on. By default, Jib uses {@link\n     * Executors#newCachedThreadPool}.\n     *\n     * @param executorService the {@link ExecutorService}\n     * @return this\n     */\n    public Builder setExecutorService(@Nullable ExecutorService executorService) {\n      this.executorService = executorService;\n      return this;\n    }\n\n    /**\n     * Sets the registry mirrors.\n     *\n     * @param registryMirrors registry mirrors\n     * @return this\n     */\n    public Builder setRegistryMirrors(ListMultimap<String, String> registryMirrors) {\n      this.registryMirrors = ImmutableListMultimap.copyOf(registryMirrors);\n      return this;\n    }\n\n    /**\n     * Builds a new {@link BuildContext} using the parameters passed into the builder.\n     *\n     * @return the corresponding build context\n     * @throws CacheDirectoryCreationException if I/O exception occurs when creating cache directory\n     */\n    public BuildContext build() throws CacheDirectoryCreationException {\n      // Validates the parameters.\n      List<String> missingFields = new ArrayList<>();\n      if (baseImageConfiguration == null) {\n        missingFields.add(\"base image configuration\");\n      }\n      if (targetImageConfiguration == null) {\n        missingFields.add(\"target image configuration\");\n      }\n      if (containerConfiguration == null) {\n        missingFields.add(\"container configuration\");\n      }\n      if (baseImageLayersCacheDirectory == null) {\n        missingFields.add(\"base image layers cache directory\");\n      }\n      if (applicationLayersCacheDirectory == null) {\n        missingFields.add(\"application layers cache directory\");\n      }\n\n      switch (missingFields.size()) {\n        case 0: // No errors\n          Preconditions.checkNotNull(baseImageConfiguration);\n          if (!baseImageConfiguration.getImage().getDigest().isPresent()\n              && !baseImageConfiguration.getImage().isScratch()) {\n            eventHandlers.dispatch(\n                LogEvent.warn(\n                    \"Base image '\"\n                        + baseImageConfiguration.getImage()\n                        + \"' does not use a specific image digest - build may not be reproducible\"));\n          }\n\n          return new BuildContext(\n              baseImageConfiguration,\n              Verify.verifyNotNull(targetImageConfiguration),\n              additionalTargetImageTags,\n              Verify.verifyNotNull(containerConfiguration),\n              Cache.withDirectory(Preconditions.checkNotNull(baseImageLayersCacheDirectory)),\n              Cache.withDirectory(Preconditions.checkNotNull(applicationLayersCacheDirectory)),\n              targetFormat,\n              offline,\n              layerConfigurations,\n              toolName,\n              toolVersion,\n              eventHandlers,\n              // TODO: try setting global User-Agent: here\n              new FailoverHttpClient(\n                  allowInsecureRegistries,\n                  JibSystemProperties.sendCredentialsOverHttp(),\n                  eventHandlers::dispatch),\n              executorService == null ? Executors.newCachedThreadPool() : executorService,\n              executorService == null, // shutDownExecutorService\n              alwaysCacheBaseImage,\n              registryMirrors,\n              enablePlatformTags);\n\n        case 1:\n          throw new IllegalStateException(missingFields.get(0) + \" is required but not set\");\n\n        case 2:\n          throw new IllegalStateException(\n              missingFields.get(0) + \" and \" + missingFields.get(1) + \" are required but not set\");\n\n        default:\n          missingFields.add(\"and \" + missingFields.remove(missingFields.size() - 1));\n          StringJoiner errorMessage = new StringJoiner(\", \", \"\", \" are required but not set\");\n          for (String missingField : missingFields) {\n            errorMessage.add(missingField);\n          }\n          throw new IllegalStateException(errorMessage.toString());\n      }\n    }\n\n    @Nullable\n    @VisibleForTesting\n    Path getBaseImageLayersCacheDirectory() {\n      return baseImageLayersCacheDirectory;\n    }\n\n    @Nullable\n    @VisibleForTesting\n    Path getApplicationLayersCacheDirectory() {\n      return applicationLayersCacheDirectory;\n    }\n  }\n\n  /**\n   * Creates a new {@link Builder} to build a {@link BuildContext}.\n   *\n   * @return a new {@link Builder}\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private final ImageConfiguration baseImageConfiguration;\n  private final ImageConfiguration targetImageConfiguration;\n  private final ImmutableSet<String> additionalTargetImageTags;\n  private final ContainerConfiguration containerConfiguration;\n  private final Cache baseImageLayersCache;\n  private final Cache applicationLayersCache;\n  private Class<? extends BuildableManifestTemplate> targetFormat;\n  private final boolean offline;\n  private final ImmutableList<FileEntriesLayer> layerConfigurations;\n  private final String toolName;\n  @Nullable private final String toolVersion;\n  private final EventHandlers eventHandlers;\n  private final FailoverHttpClient httpClient;\n  private final ExecutorService executorService;\n  private final boolean shutDownExecutorService;\n  private final boolean alwaysCacheBaseImage;\n  private final ImmutableListMultimap<String, String> registryMirrors;\n  private final boolean enablePlatformTags;\n\n  /** Instantiate with {@link #builder}. */\n  private BuildContext(\n      ImageConfiguration baseImageConfiguration,\n      ImageConfiguration targetImageConfiguration,\n      ImmutableSet<String> additionalTargetImageTags,\n      ContainerConfiguration containerConfiguration,\n      Cache baseImageLayersCache,\n      Cache applicationLayersCache,\n      Class<? extends BuildableManifestTemplate> targetFormat,\n      boolean offline,\n      ImmutableList<FileEntriesLayer> layerConfigurations,\n      String toolName,\n      @Nullable String toolVersion,\n      EventHandlers eventHandlers,\n      FailoverHttpClient httpClient,\n      ExecutorService executorService,\n      boolean shutDownExecutorService,\n      boolean alwaysCacheBaseImage,\n      ImmutableListMultimap<String, String> registryMirrors,\n      boolean enablePlatformTags) {\n    this.baseImageConfiguration = baseImageConfiguration;\n    this.targetImageConfiguration = targetImageConfiguration;\n    this.additionalTargetImageTags = additionalTargetImageTags;\n    this.containerConfiguration = containerConfiguration;\n    this.baseImageLayersCache = baseImageLayersCache;\n    this.applicationLayersCache = applicationLayersCache;\n    this.targetFormat = targetFormat;\n    this.offline = offline;\n    this.layerConfigurations = layerConfigurations;\n    this.toolName = toolName;\n    this.toolVersion = toolVersion;\n    this.eventHandlers = eventHandlers;\n    this.httpClient = httpClient;\n    this.executorService = executorService;\n    this.shutDownExecutorService = shutDownExecutorService;\n    this.alwaysCacheBaseImage = alwaysCacheBaseImage;\n    this.registryMirrors = registryMirrors;\n    this.enablePlatformTags = enablePlatformTags;\n  }\n\n  public ImageConfiguration getBaseImageConfiguration() {\n    return baseImageConfiguration;\n  }\n\n  public boolean getEnablePlatformTags() {\n    return enablePlatformTags;\n  }\n\n  public ImageConfiguration getTargetImageConfiguration() {\n    return targetImageConfiguration;\n  }\n\n  /**\n   * Returns all image tags configured for this build.\n   *\n   * @return the set of image tags configured for this build\n   */\n  public ImmutableSet<String> getAllTargetImageTags() {\n    ImmutableSet.Builder<String> allTargetImageTags =\n        ImmutableSet.builderWithExpectedSize(1 + additionalTargetImageTags.size());\n    allTargetImageTags.add(targetImageConfiguration.getImageQualifier());\n    allTargetImageTags.addAll(additionalTargetImageTags);\n    return allTargetImageTags.build();\n  }\n\n  public ContainerConfiguration getContainerConfiguration() {\n    return containerConfiguration;\n  }\n\n  public Class<? extends BuildableManifestTemplate> getTargetFormat() {\n    return targetFormat;\n  }\n\n  public String getToolName() {\n    return toolName;\n  }\n\n  @Nullable\n  public String getToolVersion() {\n    return toolVersion;\n  }\n\n  public EventHandlers getEventHandlers() {\n    return eventHandlers;\n  }\n\n  public ExecutorService getExecutorService() {\n    return executorService;\n  }\n\n  /**\n   * Gets the {@link Cache} for base image layers.\n   *\n   * @return the {@link Cache} for base image layers\n   */\n  public Cache getBaseImageLayersCache() {\n    return baseImageLayersCache;\n  }\n\n  /**\n   * Gets the {@link Cache} for application layers.\n   *\n   * @return the {@link Cache} for application layers\n   */\n  public Cache getApplicationLayersCache() {\n    return applicationLayersCache;\n  }\n\n  /**\n   * Gets whether or not to run the build in offline mode.\n   *\n   * @return {@code true} if the build will run in offline mode; {@code false} otherwise\n   */\n  public boolean isOffline() {\n    return offline;\n  }\n\n  /**\n   * Gets whether or not to force caching the base images.\n   *\n   * @return {@code true} if the user wants to force the build to always pull the image layers.\n   */\n  public boolean getAlwaysCacheBaseImage() {\n    return alwaysCacheBaseImage;\n  }\n\n  /**\n   * Gets the configurations for building the layers.\n   *\n   * @return the list of layer configurations\n   */\n  public ImmutableList<FileEntriesLayer> getLayerConfigurations() {\n    return layerConfigurations;\n  }\n\n  /**\n   * Gets the registry mirrors.\n   *\n   * @return the registry mirrors\n   */\n  public ImmutableListMultimap<String, String> getRegistryMirrors() {\n    return registryMirrors;\n  }\n\n  /**\n   * Creates a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} for the base\n   * image with fields from the build configuration. The server URL is derived from the base {@link\n   * ImageConfiguration}.\n   *\n   * @return a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory}\n   */\n  public RegistryClient.Factory newBaseImageRegistryClientFactory() {\n    return newBaseImageRegistryClientFactory(baseImageConfiguration.getImageRegistry());\n  }\n\n  /**\n   * Creates a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} for the base\n   * image repository on the registry {@code serverUrl}. Compared to @link\n   * #newBaseImageRegistryClientFactory()), this method is useful to try a mirror.\n   *\n   * @param serverUrl the server URL for the registry (for example, {@code gcr.io})\n   * @return a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory}\n   */\n  public RegistryClient.Factory newBaseImageRegistryClientFactory(String serverUrl) {\n    return RegistryClient.factory(\n            getEventHandlers(), serverUrl, baseImageConfiguration.getImageRepository(), httpClient)\n        .setUserAgent(makeUserAgent());\n  }\n\n  /**\n   * Creates a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} for the target\n   * image with fields from the build configuration.\n   *\n   * @return a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory}\n   */\n  public RegistryClient.Factory newTargetImageRegistryClientFactory() {\n    // if base and target are on the same registry, try enabling cross-repository mounts\n    if (baseImageConfiguration\n        .getImageRegistry()\n        .equals(targetImageConfiguration.getImageRegistry())) {\n      return RegistryClient.factory(\n              getEventHandlers(),\n              targetImageConfiguration.getImageRegistry(),\n              targetImageConfiguration.getImageRepository(),\n              baseImageConfiguration.getImageRepository(),\n              httpClient)\n          .setUserAgent(makeUserAgent());\n    }\n    return RegistryClient.factory(\n            getEventHandlers(),\n            targetImageConfiguration.getImageRegistry(),\n            targetImageConfiguration.getImageRepository(),\n            httpClient)\n        .setUserAgent(makeUserAgent());\n  }\n\n  @Override\n  public void close() throws IOException {\n    if (shutDownExecutorService) {\n      executorService.shutdown();\n    }\n    httpClient.shutDown();\n  }\n\n  /**\n   * The {@code User-Agent} is in the form of {@code jib <toolVersion> <toolName>}. For example:\n   * {@code jib 0.9.0 jib-maven-plugin}.\n   *\n   * @return the {@code User-Agent} header to send. The {@code User-Agent} can be disabled by\n   *     setting the system property variable {@code _JIB_DISABLE_USER_AGENT} to any non-empty\n   *     string.\n   */\n  @VisibleForTesting\n  String makeUserAgent() {\n    if (!JibSystemProperties.isUserAgentEnabled()) {\n      return \"\";\n    }\n\n    StringBuilder userAgentBuilder = new StringBuilder(\"jib\");\n    userAgentBuilder.append(\" \").append(toolVersion);\n    userAgentBuilder.append(\" \").append(toolName);\n    if (!Strings.isNullOrEmpty(System.getProperty(JibSystemProperties.UPSTREAM_CLIENT))) {\n      userAgentBuilder.append(\" \").append(System.getProperty(JibSystemProperties.UPSTREAM_CLIENT));\n    }\n    return userAgentBuilder.toString();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ContainerConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Iterables;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\n/** Immutable configuration options for the container. */\npublic class ContainerConfiguration {\n\n  /** Builder for instantiating a {@link ContainerConfiguration}. */\n  public static class Builder {\n\n    /**\n     * The default creation time of the container (constant to ensure reproducibility by default).\n     */\n    private static final Instant DEFAULT_CREATION_TIME = Instant.EPOCH;\n\n    // LinkedHashSet to preserve the order\n    private Set<Platform> platforms =\n        new LinkedHashSet<>(Collections.singleton(new Platform(\"amd64\", \"linux\")));\n    private Instant creationTime = DEFAULT_CREATION_TIME;\n    @Nullable private ImmutableList<String> entrypoint;\n    @Nullable private ImmutableList<String> programArguments;\n    @Nullable private Map<String, String> environmentMap;\n    @Nullable private Set<Port> exposedPorts;\n    @Nullable private Set<AbsoluteUnixPath> volumes;\n    @Nullable private Map<String, String> labels;\n    @Nullable private String user;\n    @Nullable private AbsoluteUnixPath workingDirectory;\n\n    /**\n     * Sets a desired platform (properties including OS and architecture) list. If the base image\n     * reference is a Docker manifest list or an OCI image index, an image builder may select the\n     * base images matching the given platforms. If the base image reference is an image manifest,\n     * an image builder may ignore the given platforms and use the platform of the base image or may\n     * decide to raise on error.\n     *\n     * <p>Note that a new container configuration starts with \"amd64/linux\" as the default platform.\n     *\n     * @param platforms list of platforms to select base images in case of a manifest list\n     * @return this\n     */\n    public Builder setPlatforms(Set<Platform> platforms) {\n      Preconditions.checkArgument(!platforms.isEmpty(), \"platforms set cannot be empty\");\n      this.platforms = new LinkedHashSet<>(platforms);\n      return this;\n    }\n\n    /**\n     * Adds a desired image platform (OS and architecture pair). If the base image reference is a\n     * Docker manifest list or an OCI image index, an image builder may select the base image\n     * matching the given platform. If the base image reference is an image manifest, an image\n     * builder may ignore the given platform and use the platform of the base image or may decide to\n     * raise on error.\n     *\n     * <p>Note that a new container configuration starts with \"amd64/linux\" as the default platform.\n     * If you want to reset the default platform instead of adding a new one, use {@link\n     * #setPlatforms(Set)}.\n     *\n     * @param architecture architecture (for example, {@code amd64}) to select a base image in case\n     *     of a manifest list\n     * @param os OS (for example, {@code linux}) to select a base image in case of a manifest list\n     * @return this\n     */\n    public Builder addPlatform(String architecture, String os) {\n      platforms.add(new Platform(architecture, os));\n      return this;\n    }\n\n    /**\n     * Sets the image creation time.\n     *\n     * @param creationTime the creation time\n     * @return this\n     */\n    public Builder setCreationTime(Instant creationTime) {\n      this.creationTime = creationTime;\n      return this;\n    }\n\n    /**\n     * Sets the commandline arguments for main.\n     *\n     * @param programArguments the list of arguments\n     * @return this\n     */\n    public Builder setProgramArguments(@Nullable List<String> programArguments) {\n      if (programArguments == null) {\n        this.programArguments = null;\n      } else {\n        Preconditions.checkArgument(\n            programArguments.stream().allMatch(Objects::nonNull),\n            \"program arguments list contains null elements\");\n        this.programArguments = ImmutableList.copyOf(programArguments);\n      }\n      return this;\n    }\n\n    /**\n     * Sets the container's environment variables, mapping variable name to value.\n     *\n     * @param environmentMap the map\n     * @return this\n     */\n    public Builder setEnvironment(@Nullable Map<String, String> environmentMap) {\n      if (environmentMap == null) {\n        this.environmentMap = null;\n      } else {\n        Preconditions.checkArgument(\n            !Iterables.any(environmentMap.keySet(), Objects::isNull),\n            \"environment map contains null keys\");\n        String nullValuedKeys =\n            environmentMap.entrySet().stream()\n                .filter(entry -> entry.getValue() == null)\n                .map(Map.Entry::getKey)\n                .collect(Collectors.joining(\", \"));\n        Preconditions.checkArgument(\n            nullValuedKeys.isEmpty(),\n            \"environment map contains null values for key(s): \" + nullValuedKeys);\n        this.environmentMap = new HashMap<>(environmentMap);\n      }\n      return this;\n    }\n\n    /**\n     * Adds an environment entry to the container configuration.\n     *\n     * @param name the environment variable key\n     * @param value the non-null value to associate with environment variable\n     */\n    public void addEnvironment(String name, String value) {\n      if (environmentMap == null) {\n        environmentMap = new HashMap<>();\n      }\n      environmentMap.put(name, value);\n    }\n\n    /**\n     * Sets the container's exposed ports.\n     *\n     * @param exposedPorts the set of ports\n     * @return this\n     */\n    public Builder setExposedPorts(@Nullable Set<Port> exposedPorts) {\n      if (exposedPorts == null) {\n        this.exposedPorts = null;\n      } else {\n        Preconditions.checkArgument(\n            exposedPorts.stream().allMatch(Objects::nonNull), \"ports list contains null elements\");\n        this.exposedPorts = new HashSet<>(exposedPorts);\n      }\n      return this;\n    }\n\n    /**\n     * Adds an exposed port entry to the container configuration.\n     *\n     * @param port the non-null port to add\n     */\n    public void addExposedPort(Port port) {\n      if (exposedPorts == null) {\n        exposedPorts = new HashSet<>();\n      }\n      exposedPorts.add(port);\n    }\n\n    /**\n     * Sets the container's volumes.\n     *\n     * @param volumes the set of volumes\n     * @return this\n     */\n    public Builder setVolumes(@Nullable Set<AbsoluteUnixPath> volumes) {\n      if (volumes == null) {\n        this.volumes = null;\n      } else {\n        Preconditions.checkArgument(\n            volumes.stream().allMatch(Objects::nonNull), \"volumes list contains null elements\");\n        this.volumes = new HashSet<>(volumes);\n      }\n      return this;\n    }\n\n    /**\n     * Adds a volume entry to the container configuration.\n     *\n     * @param volume the absolute path to add as a volume entry\n     */\n    public void addVolume(AbsoluteUnixPath volume) {\n      if (volumes == null) {\n        volumes = new HashSet<>();\n      }\n      volumes.add(volume);\n    }\n\n    /**\n     * Sets the container's labels.\n     *\n     * @param labels the map of labels\n     * @return this\n     */\n    public Builder setLabels(@Nullable Map<String, String> labels) {\n      if (labels == null) {\n        this.labels = null;\n      } else {\n        Preconditions.checkArgument(\n            !Iterables.any(labels.keySet(), Objects::isNull), \"labels map contains null keys\");\n        Preconditions.checkArgument(\n            !Iterables.any(labels.values(), Objects::isNull), \"labels map contains null values\");\n        this.labels = new HashMap<>(labels);\n      }\n      return this;\n    }\n\n    /**\n     * Add a label to the container configuration.\n     *\n     * @param key the label name to add\n     * @param value the value to be associated with the label\n     */\n    public void addLabel(String key, String value) {\n      if (labels == null) {\n        labels = new HashMap<>();\n      }\n      labels.put(key, value);\n    }\n\n    /**\n     * Sets the container entrypoint.\n     *\n     * @param entrypoint the tokenized command to run when the container starts\n     * @return this\n     */\n    public Builder setEntrypoint(@Nullable List<String> entrypoint) {\n      if (entrypoint == null) {\n        this.entrypoint = null;\n      } else {\n        Preconditions.checkArgument(\n            entrypoint.stream().allMatch(Objects::nonNull), \"entrypoint contains null elements\");\n        this.entrypoint = ImmutableList.copyOf(entrypoint);\n      }\n      return this;\n    }\n\n    /**\n     * Sets the user and group to run the container as. {@code user} can be a username or UID along\n     * with an optional groupname or GID. The following are all valid: {@code user}, {@code uid},\n     * {@code user:group}, {@code uid:gid}, {@code uid:group}, {@code user:gid}.\n     *\n     * @param user the username/UID and optionally the groupname/GID\n     * @return this\n     */\n    public Builder setUser(@Nullable String user) {\n      this.user = user;\n      return this;\n    }\n\n    /**\n     * Sets the working directory in the container.\n     *\n     * @param workingDirectory the working directory\n     * @return this\n     */\n    public Builder setWorkingDirectory(@Nullable AbsoluteUnixPath workingDirectory) {\n      this.workingDirectory = workingDirectory;\n      return this;\n    }\n\n    /**\n     * Builds the {@link ContainerConfiguration}.\n     *\n     * @return the corresponding {@link ContainerConfiguration}\n     */\n    public ContainerConfiguration build() {\n      return new ContainerConfiguration(\n          ImmutableSet.copyOf(platforms),\n          creationTime,\n          entrypoint,\n          programArguments,\n          environmentMap == null ? null : ImmutableMap.copyOf(environmentMap),\n          exposedPorts == null ? null : ImmutableSet.copyOf(exposedPorts),\n          volumes == null ? null : ImmutableSet.copyOf(volumes),\n          labels == null ? null : ImmutableMap.copyOf(labels),\n          user,\n          workingDirectory);\n    }\n\n    private Builder() {}\n  }\n\n  /**\n   * Constructs a builder for a {@link ContainerConfiguration}.\n   *\n   * @return the builder\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private final ImmutableSet<Platform> platforms;\n  private final Instant creationTime;\n  @Nullable private final ImmutableList<String> entrypoint;\n  @Nullable private final ImmutableList<String> programArguments;\n  @Nullable private final ImmutableMap<String, String> environmentMap;\n  @Nullable private final ImmutableSet<Port> exposedPorts;\n  @Nullable private final ImmutableSet<AbsoluteUnixPath> volumes;\n  @Nullable private final ImmutableMap<String, String> labels;\n  @Nullable private final String user;\n  @Nullable private final AbsoluteUnixPath workingDirectory;\n\n  private ContainerConfiguration(\n      ImmutableSet<Platform> platforms,\n      Instant creationTime,\n      @Nullable ImmutableList<String> entrypoint,\n      @Nullable ImmutableList<String> programArguments,\n      @Nullable ImmutableMap<String, String> environmentMap,\n      @Nullable ImmutableSet<Port> exposedPorts,\n      @Nullable ImmutableSet<AbsoluteUnixPath> volumes,\n      @Nullable ImmutableMap<String, String> labels,\n      @Nullable String user,\n      @Nullable AbsoluteUnixPath workingDirectory) {\n    this.platforms = platforms;\n    this.creationTime = creationTime;\n    this.entrypoint = entrypoint;\n    this.programArguments = programArguments;\n    this.environmentMap = environmentMap;\n    this.exposedPorts = exposedPorts;\n    this.volumes = volumes;\n    this.labels = labels;\n    this.user = user;\n    this.workingDirectory = workingDirectory;\n  }\n\n  public ImmutableSet<Platform> getPlatforms() {\n    return platforms;\n  }\n\n  public Instant getCreationTime() {\n    return creationTime;\n  }\n\n  @Nullable\n  public ImmutableList<String> getEntrypoint() {\n    return entrypoint;\n  }\n\n  @Nullable\n  public ImmutableList<String> getProgramArguments() {\n    return programArguments;\n  }\n\n  @Nullable\n  public ImmutableMap<String, String> getEnvironmentMap() {\n    return environmentMap;\n  }\n\n  @Nullable\n  public ImmutableSet<Port> getExposedPorts() {\n    return exposedPorts;\n  }\n\n  @Nullable\n  public ImmutableSet<AbsoluteUnixPath> getVolumes() {\n    return volumes;\n  }\n\n  @Nullable\n  public String getUser() {\n    return user;\n  }\n\n  @Nullable\n  public ImmutableMap<String, String> getLabels() {\n    return labels;\n  }\n\n  @Nullable\n  public AbsoluteUnixPath getWorkingDirectory() {\n    return workingDirectory;\n  }\n\n  @Override\n  @VisibleForTesting\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof ContainerConfiguration)) {\n      return false;\n    }\n    ContainerConfiguration otherContainerConfiguration = (ContainerConfiguration) other;\n    return platforms.equals(otherContainerConfiguration.platforms)\n        && creationTime.equals(otherContainerConfiguration.creationTime)\n        && Objects.equals(entrypoint, otherContainerConfiguration.entrypoint)\n        && Objects.equals(programArguments, otherContainerConfiguration.programArguments)\n        && Objects.equals(environmentMap, otherContainerConfiguration.environmentMap)\n        && Objects.equals(exposedPorts, otherContainerConfiguration.exposedPorts)\n        && Objects.equals(labels, otherContainerConfiguration.labels)\n        && Objects.equals(user, otherContainerConfiguration.user)\n        && Objects.equals(workingDirectory, otherContainerConfiguration.workingDirectory);\n  }\n\n  @Override\n  @VisibleForTesting\n  public int hashCode() {\n    return Objects.hash(\n        platforms,\n        creationTime,\n        entrypoint,\n        programArguments,\n        environmentMap,\n        exposedPorts,\n        labels,\n        user,\n        workingDirectory);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/configuration/DockerHealthCheck.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableList;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/** Configuration information for performing healthchecks on a Docker container. */\npublic class DockerHealthCheck {\n\n  /** Builds the immutable {@link DockerHealthCheck}. */\n  public static class Builder {\n\n    private final ImmutableList<String> command;\n    @Nullable private Duration interval;\n    @Nullable private Duration timeout;\n    @Nullable private Duration startPeriod;\n    @Nullable private Integer retries;\n\n    private Builder(ImmutableList<String> command) {\n      this.command = command;\n    }\n\n    /**\n     * Sets the time between healthchecks.\n     *\n     * @param interval the duration to wait between healthchecks.\n     * @return this\n     */\n    public Builder setInterval(Duration interval) {\n      this.interval = interval;\n      return this;\n    }\n\n    /**\n     * Sets the time until a healthcheck is considered hung.\n     *\n     * @param timeout the duration to wait until considering the healthcheck to be hung.\n     * @return this\n     */\n    public Builder setTimeout(Duration timeout) {\n      this.timeout = timeout;\n      return this;\n    }\n\n    /**\n     * Sets the initialization time to wait before using healthchecks.\n     *\n     * @param startPeriod the duration to wait before using healthchecks\n     * @return this\n     */\n    public Builder setStartPeriod(Duration startPeriod) {\n      this.startPeriod = startPeriod;\n      return this;\n    }\n\n    /**\n     * Sets the number of times to retry the healthcheck before the container is considered to be\n     * unhealthy.\n     *\n     * @param retries the number of retries before the container is considered to be unhealthy\n     * @return this\n     */\n    public Builder setRetries(int retries) {\n      this.retries = retries;\n      return this;\n    }\n\n    public DockerHealthCheck build() {\n      return new DockerHealthCheck(command, interval, timeout, startPeriod, retries);\n    }\n  }\n\n  /**\n   * Creates a new {@link DockerHealthCheck.Builder} with the specified command.\n   *\n   * @param command the command\n   * @return a new {@link DockerHealthCheck.Builder}\n   */\n  public static DockerHealthCheck.Builder fromCommand(List<String> command) {\n    Preconditions.checkArgument(!command.isEmpty(), \"command must not be empty\");\n    Preconditions.checkArgument(\n        command.stream().allMatch(Objects::nonNull), \"command must not contain null elements\");\n    return new Builder(ImmutableList.copyOf(command));\n  }\n\n  private final ImmutableList<String> command;\n  @Nullable private final Duration interval;\n  @Nullable private final Duration timeout;\n  @Nullable private final Duration startPeriod;\n  @Nullable private final Integer retries;\n\n  private DockerHealthCheck(\n      ImmutableList<String> command,\n      @Nullable Duration interval,\n      @Nullable Duration timeout,\n      @Nullable Duration startPeriod,\n      @Nullable Integer retries) {\n    this.command = command;\n    this.interval = interval;\n    this.timeout = timeout;\n    this.startPeriod = startPeriod;\n    this.retries = retries;\n  }\n\n  /**\n   * Gets the optional healthcheck command. A missing command means that it will be inherited from\n   * the base image.\n   *\n   * @return the healthcheck command\n   */\n  public List<String> getCommand() {\n    return command;\n  }\n\n  /**\n   * Gets the optional healthcheck interval. A missing command means that it will be inherited from\n   * the base image.\n   *\n   * @return the healthcheck interval\n   */\n  public Optional<Duration> getInterval() {\n    return Optional.ofNullable(interval);\n  }\n\n  /**\n   * Gets the optional healthcheck timeout. A missing command means that it will be inherited from\n   * the base image.\n   *\n   * @return the healthcheck timeout\n   */\n  public Optional<Duration> getTimeout() {\n    return Optional.ofNullable(timeout);\n  }\n\n  /**\n   * Gets the optional healthcheck start period. A missing command means that it will be inherited\n   * from the base image.\n   *\n   * @return the healthcheck start period\n   */\n  public Optional<Duration> getStartPeriod() {\n    return Optional.ofNullable(startPeriod);\n  }\n\n  /**\n   * Gets the optional healthcheck retry count. A missing command means that it will be inherited\n   * from the base image.\n   *\n   * @return the healthcheck retry count\n   */\n  public Optional<Integer> getRetries() {\n    return Optional.ofNullable(retries);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ImageConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableList;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/** Immutable configuration options for a base or target image reference. */\npublic class ImageConfiguration {\n\n  /** Builder for instantiating an {@link ImageConfiguration}. */\n  public static class Builder {\n\n    private ImageReference imageReference;\n    private ImmutableList<CredentialRetriever> credentialRetrievers = ImmutableList.of();\n    @Nullable private DockerClient dockerClient;\n    @Nullable private Path tarPath;\n\n    /**\n     * Sets the providers for registry credentials. The order determines the priority in which the\n     * retrieval methods are attempted.\n     *\n     * @param credentialRetrievers the list of {@link CredentialRetriever}s\n     * @return this\n     */\n    public Builder setCredentialRetrievers(List<CredentialRetriever> credentialRetrievers) {\n      Preconditions.checkArgument(\n          credentialRetrievers.stream().allMatch(Objects::nonNull),\n          \"credential retriever list contains null elements\");\n      this.credentialRetrievers = ImmutableList.copyOf(credentialRetrievers);\n      return this;\n    }\n\n    /**\n     * Sets the Docker client to be used for Docker daemon base images.\n     *\n     * @param dockerClient the Docker client\n     * @return this\n     */\n    public Builder setDockerClient(DockerClient dockerClient) {\n      this.dockerClient = dockerClient;\n      return this;\n    }\n\n    /**\n     * Sets the path for tarball base images.\n     *\n     * @param tarPath the path\n     * @return this\n     */\n    public Builder setTarPath(Path tarPath) {\n      this.tarPath = tarPath;\n      return this;\n    }\n\n    /**\n     * Builds the {@link ImageConfiguration}.\n     *\n     * @return the corresponding {@link ImageConfiguration}\n     */\n    public ImageConfiguration build() {\n      int numArguments = 0;\n      if (!credentialRetrievers.isEmpty()) {\n        numArguments++;\n      }\n      if (dockerClient != null) {\n        numArguments++;\n      }\n      if (tarPath != null) {\n        numArguments++;\n      }\n      Preconditions.checkArgument(numArguments <= 1);\n      return new ImageConfiguration(imageReference, credentialRetrievers, dockerClient, tarPath);\n    }\n\n    private Builder(ImageReference imageReference) {\n      this.imageReference = imageReference;\n    }\n  }\n\n  /**\n   * Constructs a builder for an {@link ImageConfiguration}.\n   *\n   * @param imageReference the image reference, which is a required field\n   * @return the builder\n   */\n  public static Builder builder(ImageReference imageReference) {\n    return new Builder(imageReference);\n  }\n\n  private final ImageReference image;\n  private final ImmutableList<CredentialRetriever> credentialRetrievers;\n  @Nullable private DockerClient dockerClient;\n  @Nullable private Path tarPath;\n\n  private ImageConfiguration(\n      ImageReference image,\n      ImmutableList<CredentialRetriever> credentialRetrievers,\n      @Nullable DockerClient dockerClient,\n      @Nullable Path tarPath) {\n    this.image = image;\n    this.credentialRetrievers = credentialRetrievers;\n    this.dockerClient = dockerClient;\n    this.tarPath = tarPath;\n  }\n\n  public ImageReference getImage() {\n    return image;\n  }\n\n  public String getImageRegistry() {\n    return image.getRegistry();\n  }\n\n  public String getImageRepository() {\n    return image.getRepository();\n  }\n\n  public String getImageQualifier() {\n    return image.getQualifier();\n  }\n\n  public ImmutableList<CredentialRetriever> getCredentialRetrievers() {\n    return credentialRetrievers;\n  }\n\n  public Optional<DockerClient> getDockerClient() {\n    return Optional.ofNullable(dockerClient);\n  }\n\n  public Optional<Path> getTarPath() {\n    return Optional.ofNullable(tarPath);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.docker;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.DockerInfoDetails;\nimport com.google.cloud.tools.jib.api.ImageDetails;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.http.NotifyingOutputStream;\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.CharStreams;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\n/** Calls out to the {@code docker} CLI. */\npublic class CliDockerClient implements DockerClient {\n\n  /**\n   * Contains the size, image ID, and diff IDs of an image inspected with {@code docker inspect}.\n   */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  public static class DockerImageDetails implements JsonTemplate, ImageDetails {\n\n    @JsonIgnoreProperties(ignoreUnknown = true)\n    private static class RootFsTemplate implements JsonTemplate {\n      @JsonProperty(\"Layers\")\n      private final List<String> layers = Collections.emptyList();\n    }\n\n    @JsonProperty(\"Size\")\n    private long size;\n\n    @JsonProperty(\"Id\")\n    private String imageId = \"\";\n\n    @JsonProperty(\"RootFS\")\n    private final RootFsTemplate rootFs = new RootFsTemplate();\n\n    @Override\n    public long getSize() {\n      return size;\n    }\n\n    @Override\n    public DescriptorDigest getImageId() throws DigestException {\n      return DescriptorDigest.fromDigest(imageId);\n    }\n\n    /**\n     * Return a list of diff ids of the layers in the image.\n     *\n     * @return a list of diff ids\n     * @throws DigestException if a digest is invalid\n     */\n    @Override\n    public List<DescriptorDigest> getDiffIds() throws DigestException {\n      List<DescriptorDigest> processedDiffIds = new ArrayList<>(rootFs.layers.size());\n      for (String diffId : rootFs.layers) {\n        processedDiffIds.add(DescriptorDigest.fromDigest(diffId.trim()));\n      }\n      return processedDiffIds;\n    }\n  }\n\n  /** Default path to the docker executable. */\n  public static final Path DEFAULT_DOCKER_CLIENT = Paths.get(\"docker\");\n\n  /**\n   * 10 minute timeout to ensure that Jib doesn't get stuck indefinitely when expecting a docker\n   * output.\n   */\n  public static final Long DOCKER_OUTPUT_TIMEOUT = (long) 10 * 60 * 1000;\n\n  /**\n   * Checks if Docker is installed on the user's system by running the `docker` command.\n   *\n   * @return {@code true} if Docker is installed on the user's system and accessible\n   */\n  public static boolean isDefaultDockerInstalled() {\n    try {\n      new ProcessBuilder(DEFAULT_DOCKER_CLIENT.toString()).start();\n      return true;\n    } catch (IOException ex) {\n      return false;\n    }\n  }\n\n  /**\n   * Checks if Docker is installed on the user's system and by verifying if the executable path\n   * provided has the appropriate permissions.\n   *\n   * @param dockerExecutable path to the executable to test running\n   * @return {@code true} if Docker is installed on the user's system and accessible\n   */\n  public static boolean isDockerInstalled(Path dockerExecutable) {\n    return Files.exists(dockerExecutable);\n  }\n\n  /**\n   * Gets a function that takes a {@code docker} subcommand and gives back a {@link ProcessBuilder}\n   * for that {@code docker} command.\n   *\n   * @param dockerExecutable path to {@code docker}\n   * @return the default {@link ProcessBuilder} factory for running a {@code docker} subcommand\n   */\n  @VisibleForTesting\n  static Function<List<String>, ProcessBuilder> defaultProcessBuilderFactory(\n      String dockerExecutable, ImmutableMap<String, String> dockerEnvironment) {\n    return dockerSubCommand -> {\n      List<String> dockerCommand = new ArrayList<>(1 + dockerSubCommand.size());\n      dockerCommand.add(dockerExecutable);\n      dockerCommand.addAll(dockerSubCommand);\n\n      ProcessBuilder processBuilder = new ProcessBuilder(dockerCommand);\n      Map<String, String> environment = processBuilder.environment();\n      environment.putAll(dockerEnvironment);\n\n      return processBuilder;\n    };\n  }\n\n  private static String getStderrOutput(Process process) {\n    try (InputStreamReader stderr =\n        new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8)) {\n      return CharStreams.toString(stderr);\n    } catch (IOException ex) {\n      return \"unknown (failed to read error message from stderr due to \" + ex.getMessage() + \")\";\n    }\n  }\n\n  /** Factory for generating the {@link ProcessBuilder} for running {@code docker} commands. */\n  private final Function<List<String>, ProcessBuilder> processBuilderFactory;\n\n  /**\n   * Instantiates with a {@code docker} executable and environment variables.\n   *\n   * @param dockerExecutable path to {@code docker}\n   * @param dockerEnvironment environment variables for {@code docker}\n   */\n  public CliDockerClient(Path dockerExecutable, Map<String, String> dockerEnvironment) {\n    this(\n        defaultProcessBuilderFactory(\n            dockerExecutable.toString(), ImmutableMap.copyOf(dockerEnvironment)));\n  }\n\n  @VisibleForTesting\n  CliDockerClient(Function<List<String>, ProcessBuilder> processBuilderFactory) {\n    this.processBuilderFactory = processBuilderFactory;\n  }\n\n  @Override\n  public boolean supported(Map<String, String> parameters) {\n    return true;\n  }\n\n  @Override\n  public DockerInfoDetails info() throws IOException, InterruptedException {\n    // Runs 'docker info'.\n    ExecutorService executor = Executors.newSingleThreadExecutor();\n    Future<DockerInfoDetails> readerFuture = executor.submit(this::fetchInfoDetails);\n    try {\n      DockerInfoDetails details = readerFuture.get(DOCKER_OUTPUT_TIMEOUT, TimeUnit.MILLISECONDS);\n      return details;\n    } catch (TimeoutException e) {\n      readerFuture.cancel(true); // Interrupt the reader thread\n      throw new IOException(\"Timeout reached while waiting for 'docker info' output\");\n    } catch (ExecutionException e) {\n      throw new IOException(\"Failed to read output of 'docker info': \" + e.getMessage());\n    } finally {\n      executor.shutdownNow();\n    }\n  }\n\n  @Override\n  public String load(ImageTarball imageTarball, Consumer<Long> writtenByteCountListener)\n      throws InterruptedException, IOException {\n    // Runs 'docker load'.\n    Process dockerProcess = docker(\"load\");\n\n    try (NotifyingOutputStream stdin =\n        new NotifyingOutputStream(dockerProcess.getOutputStream(), writtenByteCountListener)) {\n      imageTarball.writeTo(stdin);\n\n    } catch (IOException ex) {\n      // Tries to read from stderr. Not using getStderrOutput(), as we want to show the error\n      // message from the tarball I/O write failure when reading from stderr fails.\n      String error;\n      try (InputStreamReader stderr =\n          new InputStreamReader(dockerProcess.getErrorStream(), StandardCharsets.UTF_8)) {\n        error = CharStreams.toString(stderr);\n      } catch (IOException ignored) {\n        // This ignores exceptions from reading stderr and uses the original exception from\n        // writing to stdin.\n        error = ex.getMessage();\n      }\n      throw new IOException(\"'docker load' command failed with error: \" + error, ex);\n    }\n\n    try (InputStreamReader stdout =\n        new InputStreamReader(dockerProcess.getInputStream(), StandardCharsets.UTF_8)) {\n      String output = CharStreams.toString(stdout);\n\n      if (dockerProcess.waitFor() != 0) {\n        throw new IOException(\n            \"'docker load' command failed with error: \" + getStderrOutput(dockerProcess));\n      }\n\n      return output;\n    }\n  }\n\n  @Override\n  public void save(\n      ImageReference imageReference, Path outputPath, Consumer<Long> writtenByteCountListener)\n      throws InterruptedException, IOException {\n    Process dockerProcess = docker(\"save\", imageReference.toString());\n\n    try (InputStream stdout = new BufferedInputStream(dockerProcess.getInputStream());\n        OutputStream fileStream = new BufferedOutputStream(Files.newOutputStream(outputPath));\n        NotifyingOutputStream notifyingFileStream =\n            new NotifyingOutputStream(fileStream, writtenByteCountListener)) {\n      ByteStreams.copy(stdout, notifyingFileStream);\n    }\n\n    if (dockerProcess.waitFor() != 0) {\n      throw new IOException(\n          \"'docker save' command failed with error: \" + getStderrOutput(dockerProcess));\n    }\n  }\n\n  @Override\n  public DockerImageDetails inspect(ImageReference imageReference)\n      throws IOException, InterruptedException {\n    Process inspectProcess =\n        docker(\"inspect\", \"-f\", \"{{json .}}\", \"--type\", \"image\", imageReference.toString());\n\n    try (InputStreamReader stdout =\n        new InputStreamReader(inspectProcess.getInputStream(), StandardCharsets.UTF_8)) {\n      String output = CharStreams.toString(stdout);\n\n      if (inspectProcess.waitFor() != 0) {\n        throw new IOException(\n            \"'docker inspect' command failed with error: \" + getStderrOutput(inspectProcess));\n      }\n\n      return JsonTemplateMapper.readJson(output, DockerImageDetails.class);\n    }\n  }\n\n  /** Runs a {@code docker} command. */\n  private Process docker(String... subCommand) throws IOException {\n    return processBuilderFactory.apply(Arrays.asList(subCommand)).start();\n  }\n\n  private DockerInfoDetails fetchInfoDetails() throws IOException, InterruptedException {\n    Process infoProcess = docker(\"info\", \"-f\", \"{{json .}}\");\n\n    try (InputStreamReader stdout =\n        new InputStreamReader(infoProcess.getInputStream(), StandardCharsets.UTF_8)) {\n      String output = CharStreams.toString(stdout);\n\n      if (infoProcess.waitFor() != 0) {\n        throw new IOException(\n            \"'docker info' command failed with error: \" + getStderrOutput(infoProcess));\n      }\n\n      return JsonTemplateMapper.readJson(output, DockerInfoDetails.class);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClientResolver.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.docker;\n\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.ServiceLoader;\n\npublic class DockerClientResolver {\n\n  private DockerClientResolver() {}\n\n  /**\n   * Look for supported DockerClient.\n   *\n   * @param parameters needed by the dockerClient\n   * @return dockerClient if any is found\n   */\n  public static Optional<DockerClient> resolve(Map<String, String> parameters) {\n    ServiceLoader<DockerClient> dockerClients = ServiceLoader.load(DockerClient.class);\n    for (DockerClient dockerClient : dockerClients) {\n      if (dockerClient.supported(parameters)) {\n        return Optional.of(dockerClient);\n      }\n    }\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplate.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.docker.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * JSON Template for a loadable Docker Manifest entry. The RepoTags property requires a tag; i.e. if\n * a tag is missing, it explicitly should use \"latest\".\n *\n * <p>Note that this is a template for a single Manifest entry, while the entire Docker Manifest\n * should be {@code List<DockerManifestEntryTemplate>}.\n *\n * <p>Example manifest entry JSON:\n *\n * <pre>{@code\n * {\n *   \"Config\":\"config.json\",\n *   \"RepoTags\":[\"repository:tag\"]\n *   \"Layers\": [\n *     \"eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca.tar.gz\",\n *     \"ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24.tar.gz\",\n *     \"15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5.tar.gz\"\n *   ]\n * }\n * }</pre>\n *\n * @see <a href=\"https://github.com/moby/moby/blob/master/image/tarexport/load.go\">Docker load\n *     source</a>\n */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class DockerManifestEntryTemplate implements JsonTemplate {\n\n  @JsonProperty(\"Config\")\n  private String config = \"config.json\";\n\n  @JsonProperty(\"RepoTags\")\n  private final List<String> repoTags = new ArrayList<>();\n\n  @JsonProperty(\"Layers\")\n  private final List<String> layers = new ArrayList<>();\n\n  public void setConfig(String config) {\n    this.config = config;\n  }\n\n  public void addRepoTag(String repoTag) {\n    repoTags.add(repoTag);\n  }\n\n  public void addLayerFile(String layer) {\n    layers.add(layer);\n  }\n\n  public String getConfig() {\n    return config;\n  }\n\n  public List<String> getLayerFiles() {\n    return layers;\n  }\n\n  @VisibleForTesting\n  public List<String> getRepoTags() {\n    return repoTags;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/EventHandlers.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event;\n\nimport com.google.cloud.tools.jib.api.JibEvent;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ArrayListMultimap;\nimport com.google.common.collect.ImmutableMultimap;\nimport com.google.common.collect.Multimap;\nimport java.util.function.Consumer;\n\n/** Builds a set of event handlers to handle {@link JibEvent}s. */\npublic class EventHandlers {\n\n  /** Builder for {@link EventHandlers}. */\n  public static class Builder {\n\n    private final Multimap<Class<? extends JibEvent>, Handler<? extends JibEvent>> handlers =\n        ArrayListMultimap.create();\n\n    /**\n     * Adds the {@code eventConsumer} to handle the {@link JibEvent} with class {@code eventClass}.\n     * The order in which handlers are added is the order in which they are called when the event is\n     * dispatched.\n     *\n     * <p><b>Note: Implementations of {@code eventConsumer} must be thread-safe.</b>\n     *\n     * @param eventType the event type that {@code eventConsumer} should handle\n     * @param eventConsumer the event handler\n     * @param <E> the type of {@code eventClass}\n     * @return this\n     */\n    public <E extends JibEvent> Builder add(Class<E> eventType, Consumer<? super E> eventConsumer) {\n      handlers.put(eventType, new Handler<>(eventType, eventConsumer));\n      return this;\n    }\n\n    public EventHandlers build() {\n      return new EventHandlers(handlers);\n    }\n  }\n\n  /** An empty {@link EventHandlers}. */\n  public static final EventHandlers NONE = new Builder().build();\n\n  /** Maps from {@link JibEvent} class to handlers for that event type. */\n  private final ImmutableMultimap<Class<? extends JibEvent>, Handler<? extends JibEvent>> handlers;\n\n  private EventHandlers(Multimap<Class<? extends JibEvent>, Handler<? extends JibEvent>> handlers) {\n    this.handlers = ImmutableMultimap.copyOf(handlers);\n  }\n\n  /**\n   * Creates a new {@link EventHandlers.Builder}.\n   *\n   * @return the builder\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Dispatches {@code jibEvent} to all the handlers that can handle it.\n   *\n   * @param jibEvent the {@link JibEvent} to dispatch\n   */\n  public void dispatch(JibEvent jibEvent) {\n    if (handlers.isEmpty()) {\n      return;\n    }\n    handlers.get(JibEvent.class).forEach(handler -> handler.handle(jibEvent));\n    handlers.get(jibEvent.getClass()).forEach(handler -> handler.handle(jibEvent));\n  }\n\n  /**\n   * Gets the handlers added.\n   *\n   * @return the map from {@link JibEvent} type to a list of {@link Handler}s\n   */\n  @VisibleForTesting\n  ImmutableMultimap<Class<? extends JibEvent>, Handler<? extends JibEvent>> getHandlers() {\n    return ImmutableMultimap.copyOf(handlers);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/Handler.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event;\n\nimport com.google.cloud.tools.jib.api.JibEvent;\nimport com.google.common.base.Preconditions;\nimport java.util.function.Consumer;\n\n/** Handles a dispatched {@link JibEvent}. */\nclass Handler<E extends JibEvent> {\n\n  private final Class<E> eventClass;\n  private final Consumer<? super E> eventConsumer;\n\n  Handler(Class<E> eventClass, Consumer<? super E> eventConsumer) {\n    this.eventClass = eventClass;\n    this.eventConsumer = eventConsumer;\n  }\n\n  /**\n   * Handles a {@link JibEvent}.\n   *\n   * @param jibEvent the event to handle\n   */\n  void handle(JibEvent jibEvent) {\n    Preconditions.checkArgument(eventClass.isInstance(jibEvent));\n    eventConsumer.accept(eventClass.cast(jibEvent));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/events/ProgressEvent.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.events;\n\nimport com.google.cloud.tools.jib.api.JibEvent;\nimport com.google.cloud.tools.jib.event.progress.Allocation;\n\n/**\n * Event representing progress. The progress accounts for allocation units in an {@link Allocation},\n * which makes up a Decentralized Allocation Tree.\n *\n * @see Allocation\n */\npublic class ProgressEvent implements JibEvent {\n\n  /**\n   * The allocation this progress is for. Each progress unit accounts for a single allocation unit\n   * on the {@link Allocation}.\n   */\n  private final Allocation allocation;\n\n  /** Units of progress. */\n  private final long progressUnits;\n\n  public ProgressEvent(Allocation allocation, long progressUnits) {\n    this.allocation = allocation;\n    this.progressUnits = progressUnits;\n  }\n\n  /**\n   * Gets the {@link Allocation} this progress event accounts for.\n   *\n   * @return the {@link Allocation}\n   */\n  public Allocation getAllocation() {\n    return allocation;\n  }\n\n  /**\n   * Gets the units of progress this progress event accounts for in the associated {@link\n   * Allocation}.\n   *\n   * @return units of allocation\n   */\n  public long getUnits() {\n    return progressUnits;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/events/TimerEvent.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.events;\n\nimport com.google.cloud.tools.jib.api.JibEvent;\nimport java.time.Duration;\nimport java.util.Optional;\n\n/**\n * Timer event for timing various part of Jib's execution.\n *\n * <p>Timer events follow a specific {@link Timer} through a {@link State#START}, {@link State#LAP},\n * and {@link State#FINISHED} states. The duration indicates the duration since the last {@link\n * TimerEvent} dispatched for the {@link Timer}.\n *\n * <p>Timers can also define a hierarchy.\n */\npublic class TimerEvent implements JibEvent {\n\n  /** The state of the timing. */\n  public enum State {\n\n    /** The timer has started timing. {@link #getDuration} is 0. {@link #getElapsed} is 0. */\n    START,\n\n    /**\n     * The timer timed a lap. {@link #getDuration} is the time since the last event. {@link\n     * #getElapsed} is the total elapsed time.\n     */\n    LAP,\n\n    /**\n     * The timer has finished timing. {@link #getDuration} is the time since the last event. {@link\n     * #getElapsed} is the total elapsed time.\n     */\n    FINISHED\n  }\n\n  /** Defines a timer hierarchy. */\n  public interface Timer {\n\n    /**\n     * Gets the parent of this {@link Timer}.\n     *\n     * @return the parent of this {@link Timer}\n     */\n    Optional<? extends Timer> getParent();\n  }\n\n  private final State state;\n  private final Timer timer;\n  private final Duration duration;\n  private final Duration elapsed;\n  private final String description;\n\n  /**\n   * Creates a new {@link TimerEvent}. For internal use only.\n   *\n   * @param state the state of the {@link Timer}\n   * @param timer the {@link Timer}\n   * @param duration the lap duration\n   * @param elapsed the total elapsed time since the timer was created\n   * @param description the description of this event\n   */\n  public TimerEvent(\n      State state, Timer timer, Duration duration, Duration elapsed, String description) {\n    this.state = state;\n    this.timer = timer;\n    this.duration = duration;\n    this.elapsed = elapsed;\n    this.description = description;\n  }\n\n  /**\n   * Gets the state of the timer.\n   *\n   * @return the state of the timer\n   * @see State\n   */\n  public State getState() {\n    return state;\n  }\n\n  /**\n   * Gets the timer this event is for.\n   *\n   * @return the timer\n   */\n  public Timer getTimer() {\n    return timer;\n  }\n\n  /**\n   * Gets the duration since the last {@link TimerEvent} for this timer.\n   *\n   * @return the duration since the last {@link TimerEvent} for this timer.\n   */\n  public Duration getDuration() {\n    return duration;\n  }\n\n  /**\n   * Gets the total elapsed duration since this timer was created.\n   *\n   * @return the duration since this timer was created\n   */\n  public Duration getElapsed() {\n    return elapsed;\n  }\n\n  /**\n   * Gets the description associated with this event.\n   *\n   * @return the description\n   */\n  public String getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/progress/Allocation.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * Represents a Decentralized Allocation Tree (DAT) node.\n *\n * <p>A DAT node is immutable and pointers only go in the direction from child to parent. Each node\n * has a set number of allocated units, the total of which represents a single allocation unit of\n * its parent. Each node is therefore a sub-allocation of its parent node. This allows the DAT to\n * sub-allocate progress in a decentralized, asynchronous manner.\n *\n * <p>For example, thread 1 creates node A as the root node with 2 allocation units. A subtask is\n * launched on thread 1 and creates node B with 3 allocation units as a child of node A. Thread 1\n * then also launches a subtask on thread 2 that creates node C with 5 allocation units. Once the\n * first subtask finishes and reports its progress, that completion would entail completion of 3\n * allocation units of node B and 1 allocation unit of node A. The second subtask finishes and\n * reports its progress as well, indicating completion of 5 units of node C and thus 1 unit of node\n * A. Allocation A is then deemed complete as well in terms of overall progress.\n *\n * <p>Note that it is up to the user of the class to ensure that the number of sub-allocations does\n * not exceed the number of allocation units.\n */\npublic class Allocation {\n\n  /**\n   * Creates a new root {@link Allocation}.\n   *\n   * @param description user-facing description of what the allocation represents\n   * @param allocationUnits number of allocation units\n   * @return a new {@link Allocation}\n   */\n  public static Allocation newRoot(String description, long allocationUnits) {\n    return new Allocation(description, allocationUnits, null);\n  }\n\n  /** The parent {@link Allocation}, or {@code null} to indicate a root node. */\n  @Nullable private final Allocation parent;\n\n  /** User-facing description of what the allocation represents. */\n  private final String description;\n\n  /** The number of allocation units this node holds. */\n  private final long allocationUnits;\n\n  /** How much of the root allocation (1.0) each allocation unit accounts for. */\n  private final double fractionOfRoot;\n\n  private Allocation(String description, long allocationUnits, @Nullable Allocation parent) {\n    this.description = description;\n    this.allocationUnits = allocationUnits < 1 ? 1 : allocationUnits;\n    this.parent = parent;\n\n    this.fractionOfRoot = (parent == null ? 1.0 : parent.fractionOfRoot) / this.allocationUnits;\n  }\n\n  /**\n   * Creates a new child {@link Allocation} (sub-allocation).\n   *\n   * @param description user-facing description of what the sub-allocation represents\n   * @param allocationUnits number of allocation units the child holds\n   * @return a new {@link Allocation}\n   */\n  public Allocation newChild(String description, long allocationUnits) {\n    return new Allocation(description, allocationUnits, this);\n  }\n\n  /**\n   * Gets the parent allocation, or {@link Optional#empty} if this is a root allocation. This\n   * allocation represents 1 allocation unit of the parent allocation.\n   *\n   * @return the parent {@link Allocation}\n   */\n  public Optional<Allocation> getParent() {\n    return Optional.ofNullable(parent);\n  }\n\n  /**\n   * Gets a user-facing description of what this allocation represents. For example, this can a\n   * description of the task this allocation represents.\n   *\n   * @return the description\n   */\n  public String getDescription() {\n    return description;\n  }\n\n  /**\n   * Gets the allocation units this allocation holds. If this allocation is not the root, the full\n   * number of units represents 1 allocation unit of the parent.\n   *\n   * @return the allocation units\n   */\n  public long getAllocationUnits() {\n    return allocationUnits;\n  }\n\n  /**\n   * Gets how much of the root allocation each of the allocation units of this allocation accounts\n   * for. The entire root allocation is {@code 1.0}.\n   *\n   * @return the fraction of the root allocation this allocation's allocation units accounts for\n   */\n  public double getFractionOfRoot() {\n    return fractionOfRoot;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/progress/AllocationCompletionTracker.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableList;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.stream.Collectors;\n\n/**\n * Keeps track of the progress for {@link Allocation}s as well as their order in which they appear.\n *\n * <p>This implementation is thread-safe.\n */\nclass AllocationCompletionTracker {\n\n  /**\n   * Holds the progress units remaining along with a creation order (index starting from 0). This is\n   * used as the value of the {@link #completionMap}.\n   */\n  private static class IndexedRemainingUnits implements Comparable<IndexedRemainingUnits> {\n\n    /** Monotonically-increasing source for {@link #index}. */\n    private static final AtomicInteger currentIndex = new AtomicInteger();\n\n    /** The creation order that monotonically increases. */\n    private final int index = currentIndex.getAndIncrement();\n\n    /**\n     * Remaining progress units until completion. This can be shared across multiple threads and\n     * should be updated atomically.\n     */\n    private final AtomicLong remainingUnits;\n\n    private final Allocation allocation;\n\n    private IndexedRemainingUnits(Allocation allocation) {\n      this.allocation = allocation;\n      remainingUnits = new AtomicLong(allocation.getAllocationUnits());\n    }\n\n    private boolean isUnfinished() {\n      return remainingUnits.get() != 0;\n    }\n\n    @Override\n    public int compareTo(IndexedRemainingUnits otherIndexedRemainingUnits) {\n      return index - otherIndexedRemainingUnits.index;\n    }\n  }\n\n  /**\n   * Maps from {@link Allocation} to 1) the number of progress units remaining in that {@link\n   * Allocation}; as well as 2) the insertion order of the key.\n   */\n  private final ConcurrentHashMap<Allocation, IndexedRemainingUnits> completionMap =\n      new ConcurrentHashMap<>();\n\n  /**\n   * Updates the progress for {@link Allocation} atomically relative to the {@code allocation}.\n   *\n   * <p>For any {@link Allocation}, this method <em>must</em> have been called on all of its parents\n   * beforehand.\n   *\n   * @param allocation the {@link Allocation} to update progress for\n   * @param units the units of progress\n   * @return {@code true} if the map was updated\n   */\n  boolean updateProgress(Allocation allocation, long units) {\n    AtomicBoolean mapUpdated = new AtomicBoolean(units != 0);\n\n    completionMap.compute(\n        allocation,\n        (ignored, indexedRemainingUnits) -> {\n          if (indexedRemainingUnits == null) {\n            indexedRemainingUnits = new IndexedRemainingUnits(allocation);\n            mapUpdated.set(true);\n          }\n\n          if (units != 0) {\n            updateIndexedRemainingUnits(indexedRemainingUnits, units);\n          }\n          return indexedRemainingUnits;\n        });\n    return mapUpdated.get();\n  }\n\n  /**\n   * Gets a list of the unfinished {@link Allocation}s in the order in which those {@link\n   * Allocation}s were encountered. This can be used to display, for example, currently executing\n   * tasks. The order helps to keep the displayed tasks in a deterministic order (new subtasks\n   * appear below older ones) and not jumbled together in some random order.\n   *\n   * @return a list of unfinished {@link Allocation}s\n   */\n  @VisibleForTesting\n  List<Allocation> getUnfinishedAllocations() {\n    return completionMap.values().stream()\n        .filter(IndexedRemainingUnits::isUnfinished)\n        .sorted()\n        .map(remainingUnits -> remainingUnits.allocation)\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Helper method for {@link #updateProgress(Allocation, long)}. Subtract {@code units} from {@code\n   * indexedRemainingUnits}. Updates {@link IndexedRemainingUnits} for parent {@link Allocation}s if\n   * remaining units becomes 0. This method is <em>not</em> thread-safe for the {@code\n   * indexedRemainingUnits} and should be called atomically relative to the {@code\n   * indexedRemainingUnits}.\n   *\n   * @param indexedRemainingUnits the {@link IndexedRemainingUnits} to update progress for\n   * @param units the units of progress\n   */\n  private void updateIndexedRemainingUnits(\n      IndexedRemainingUnits indexedRemainingUnits, long units) {\n    Allocation allocation = indexedRemainingUnits.allocation;\n\n    long newUnits = indexedRemainingUnits.remainingUnits.addAndGet(-units);\n    if (newUnits < 0L) {\n      throw new IllegalStateException(\n          \"Progress exceeds max for '\"\n              + allocation.getDescription()\n              + \"': \"\n              + -newUnits\n              + \" more beyond \"\n              + allocation.getAllocationUnits());\n    }\n\n    // Updates the parent allocations if this allocation completed.\n    if (newUnits == 0L) {\n      allocation\n          .getParent()\n          .ifPresent(\n              parentAllocation ->\n                  updateIndexedRemainingUnits(\n                      Preconditions.checkNotNull(completionMap.get(parentAllocation)), 1L));\n    }\n  }\n\n  ImmutableList<String> getUnfinishedLeafTasks() {\n    List<Allocation> allUnfinished = getUnfinishedAllocations();\n    Set<Allocation> unfinishedLeaves = new LinkedHashSet<>(allUnfinished); // preserves order\n\n    for (Allocation allocation : allUnfinished) {\n      Optional<Allocation> parent = allocation.getParent();\n\n      while (parent.isPresent()) {\n        unfinishedLeaves.remove(parent.get());\n        parent = parent.get().getParent();\n      }\n    }\n\n    return ImmutableList.copyOf(\n        unfinishedLeaves.stream().map(Allocation::getDescription).collect(Collectors.toList()));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/progress/ProgressEventHandler.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.common.collect.ImmutableList;\nimport java.util.concurrent.atomic.DoubleAdder;\nimport java.util.function.Consumer;\n\n/**\n * Handles {@link ProgressEvent}s by accumulating an overall progress and keeping track of which\n * {@link Allocation}s are complete.\n *\n * <p>This implementation is thread-safe.\n */\npublic class ProgressEventHandler implements Consumer<ProgressEvent> {\n\n  /**\n   * Contains the accumulated progress and which \"leaf\" tasks are not yet complete. Leaf tasks are\n   * those that do not have sub-tasks.\n   */\n  public static class Update {\n\n    private final double progress;\n    private final ImmutableList<String> unfinishedLeafTasks;\n\n    private Update(double progress, ImmutableList<String> unfinishedLeafTasks) {\n      this.progress = progress;\n      this.unfinishedLeafTasks = unfinishedLeafTasks;\n    }\n\n    /**\n     * Gets the overall progress, with {@code 1.0} meaning fully complete.\n     *\n     * @return the overall progress\n     */\n    public double getProgress() {\n      return progress;\n    }\n\n    /**\n     * Gets a list of the unfinished \"leaf\" tasks in the order in which those tasks were\n     * encountered.\n     *\n     * @return a list of unfinished \"leaf\" tasks\n     */\n    public ImmutableList<String> getUnfinishedLeafTasks() {\n      return unfinishedLeafTasks;\n    }\n  }\n\n  /** Keeps track of the progress for each {@link Allocation} encountered. */\n  private final AllocationCompletionTracker completionTracker = new AllocationCompletionTracker();\n\n  /** Accumulates an overall progress, with {@code 1.0} indicating full completion. */\n  private final DoubleAdder progress = new DoubleAdder();\n\n  /**\n   * A callback to notify that {@link #progress} or {@link #completionTracker} could have changed.\n   * Note that every change will be reported (though multiple could be reported together), and there\n   * could be false positives.\n   */\n  private final Consumer<Update> updateNotifier;\n\n  public ProgressEventHandler(Consumer<Update> updateNotifier) {\n    this.updateNotifier = updateNotifier;\n  }\n\n  @Override\n  public void accept(ProgressEvent progressEvent) {\n    Allocation allocation = progressEvent.getAllocation();\n    long progressUnits = progressEvent.getUnits();\n    double allocationFraction = allocation.getFractionOfRoot();\n\n    if (progressUnits != 0) {\n      progress.add(progressUnits * allocationFraction);\n    }\n\n    if (completionTracker.updateProgress(allocation, progressUnits)) {\n      // Note: Could produce false positives.\n      updateNotifier.accept(new Update(progress.sum(), completionTracker.getUnfinishedLeafTasks()));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/event/progress/ThrottledAccumulatingConsumer.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport java.io.Closeable;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport javax.annotation.Nullable;\n\n/**\n * Wraps a {@code Consumer<Long>} so that multiple consume calls ({@link #accept}) within a short\n * period of time are merged into a single later call with the value accumulated up to that point.\n */\npublic class ThrottledAccumulatingConsumer implements Consumer<Long>, Closeable {\n\n  private final Consumer<Long> consumer;\n\n  /** Delay between each call to the underlying {@link #accept}. */\n  private final Duration delayBetweenCallbacks;\n\n  /** Last time the underlying {@link #accept} was called. */\n  private Instant previousCallback;\n\n  /** \"Clock\" that returns the current {@link Instant}. */\n  private final Supplier<Instant> getNow;\n\n  @Nullable private Long valueSoFar;\n\n  /**\n   * Wraps a consumer with the delay of 100 ms.\n   *\n   * @param callback {@link Consumer} callback to wrap\n   */\n  public ThrottledAccumulatingConsumer(Consumer<Long> callback) {\n    this(callback, Duration.ofMillis(100), Instant::now);\n  }\n\n  /**\n   * A new configured consumer.\n   *\n   * @param consumer {@link Consumer} callback to wrap\n   * @param delayBetweenCallbacks delay between each call to the underlying accept\n   * @param getNow supplies of the current time {@link Instant}\n   */\n  public ThrottledAccumulatingConsumer(\n      Consumer<Long> consumer, Duration delayBetweenCallbacks, Supplier<Instant> getNow) {\n    this.consumer = consumer;\n    this.delayBetweenCallbacks = delayBetweenCallbacks;\n    this.getNow = getNow;\n\n    previousCallback = getNow.get();\n  }\n\n  @Override\n  public void accept(Long value) {\n    valueSoFar = valueSoFar == null ? value : valueSoFar + value;\n\n    Instant now = getNow.get();\n    Instant nextFireTime = previousCallback.plus(delayBetweenCallbacks);\n    if (now.isAfter(nextFireTime)) {\n      consumer.accept(valueSoFar);\n      previousCallback = now;\n      valueSoFar = null;\n    }\n  }\n\n  @Override\n  public void close() {\n    if (valueSoFar != null) {\n      consumer.accept(valueSoFar);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/filesystem/DirectoryWalker.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.NotDirectoryException;\nimport java.nio.file.Path;\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\n/** Recursively applies a function to each file in a directory. */\npublic class DirectoryWalker {\n\n  private final Path rootDir;\n\n  private Predicate<Path> pathFilter = path -> true;\n\n  /**\n   * Initialize with a root directory to walk.\n   *\n   * @param rootDir the root directory.\n   * @throws NotDirectoryException if the root directory is not a directory.\n   */\n  public DirectoryWalker(Path rootDir) throws NotDirectoryException {\n    if (!Files.isDirectory(rootDir)) {\n      throw new NotDirectoryException(rootDir + \" is not a directory\");\n    }\n    this.rootDir = rootDir;\n  }\n\n  /**\n   * Adds a filter to the walked paths.\n   *\n   * @param pathFilter the filter. {@code pathFilter} returns {@code true} if the path should be\n   *     accepted and {@code false} otherwise.\n   * @return this\n   */\n  public DirectoryWalker filter(Predicate<Path> pathFilter) {\n    this.pathFilter = this.pathFilter.and(pathFilter);\n    return this;\n  }\n\n  /**\n   * Filters away the {@code rootDir}.\n   *\n   * @return this\n   */\n  public DirectoryWalker filterRoot() {\n    filter(path -> !path.equals(rootDir));\n    return this;\n  }\n\n  /**\n   * Walks {@link #rootDir} and applies {@code pathConsumer} to each file. Note that {@link\n   * #rootDir} itself is visited as well.\n   *\n   * @param pathConsumer the consumer that is applied to each file.\n   * @return a list of Paths that were walked.\n   * @throws IOException if the walk fails.\n   */\n  public ImmutableList<Path> walk(PathConsumer pathConsumer) throws IOException {\n    ImmutableList<Path> files = walk();\n    for (Path path : files) {\n      pathConsumer.accept(path);\n    }\n    return files;\n  }\n\n  /**\n   * Walks {@link #rootDir}.\n   *\n   * @return the walked files.\n   * @throws IOException if walking the files fails.\n   */\n  public ImmutableList<Path> walk() throws IOException {\n    try (Stream<Path> fileStream = Files.walk(rootDir)) {\n      return fileStream.filter(pathFilter).sorted().collect(ImmutableList.toImmutableList());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/filesystem/FileOperations.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\n/** Static methods for operating on the filesystem. */\npublic class FileOperations {\n\n  /**\n   * Copies {@code sourceFiles} to the {@code destDir} directory.\n   *\n   * @param sourceFiles the list of source files.\n   * @param destDir the directory to copy the files to.\n   * @throws IOException if the copy fails.\n   */\n  public static void copy(ImmutableList<Path> sourceFiles, Path destDir) throws IOException {\n    for (Path sourceFile : sourceFiles) {\n      PathConsumer copyPathConsumer =\n          path -> {\n            // Creates the same path in the destDir.\n            Path parent = Verify.verifyNotNull(sourceFile.getParent());\n            Path destPath = destDir.resolve(parent.relativize(path));\n            if (Files.isDirectory(path)) {\n              Files.createDirectories(destPath);\n            } else {\n              Files.copy(path, destPath);\n            }\n          };\n\n      if (Files.isDirectory(sourceFile)) {\n        new DirectoryWalker(sourceFile).walk(copyPathConsumer);\n      } else {\n        copyPathConsumer.accept(sourceFile);\n      }\n    }\n  }\n\n  private FileOperations() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/filesystem/LockFile.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.base.Preconditions;\nimport java.io.Closeable;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.channels.FileLock;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/** Creates and deletes lock files. */\npublic class LockFile implements Closeable {\n\n  private static final ConcurrentHashMap<Path, Lock> lockMap = new ConcurrentHashMap<>();\n\n  private final Path lockFilePath;\n  private final FileLock fileLock;\n  private final OutputStream outputStream;\n\n  private LockFile(Path lockFilePath, FileLock fileLock, OutputStream outputStream) {\n    this.lockFilePath = lockFilePath;\n    this.fileLock = fileLock;\n    this.outputStream = outputStream;\n  }\n\n  /**\n   * Creates a lock file.\n   *\n   * @param lockFile the path of the lock file\n   * @return a new {@link LockFile} that can be released later\n   * @throws IOException if creating the lock file fails\n   */\n  public static LockFile lock(Path lockFile) throws IOException {\n    try {\n      // This first lock is to prevent multiple threads from calling FileChannel.lock(), which would\n      // otherwise throw OverlappingFileLockException\n      lockMap.computeIfAbsent(lockFile, key -> new ReentrantLock()).lockInterruptibly();\n\n    } catch (InterruptedException ex) {\n      Thread.currentThread().interrupt();\n      throw new IOException(\"Interrupted while trying to acquire lock\", ex);\n    }\n\n    Files.createDirectories(lockFile.getParent());\n    FileOutputStream outputStream = new FileOutputStream(lockFile.toFile());\n    FileLock fileLock = null;\n    try {\n      fileLock = outputStream.getChannel().lock();\n      return new LockFile(lockFile, fileLock, outputStream);\n\n    } finally {\n      if (fileLock == null) {\n        outputStream.close();\n      }\n    }\n  }\n\n  /** Releases the lock file. */\n  @Override\n  public void close() {\n    try {\n      fileLock.release();\n      outputStream.close();\n\n    } catch (IOException ex) {\n      throw new IllegalStateException(\"Unable to release lock\", ex);\n\n    } finally {\n      Preconditions.checkNotNull(lockMap.get(lockFilePath)).unlock();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/filesystem/PathConsumer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\n@FunctionalInterface\npublic interface PathConsumer {\n\n  void accept(Path path) throws IOException;\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/filesystem/TempDirectoryProvider.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.io.MoreFiles;\nimport com.google.common.io.RecursiveDeleteOption;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/** Creates temporary directories and deletes them all when closed. */\npublic class TempDirectoryProvider implements Closeable {\n\n  private final Set<Path> directories = Collections.synchronizedSet(new HashSet<>());\n\n  /**\n   * Creates a new temporary directory.\n   *\n   * @return the path to the temporary directory\n   * @throws IOException if creating the directory fails\n   */\n  public Path newDirectory() throws IOException {\n    Path path = Files.createTempDirectory(null);\n    directories.add(path);\n    return path;\n  }\n\n  /**\n   * Creates a new temporary directory.\n   *\n   * @param parentDirectory the directory to create the temp directory inside\n   * @return the path to the temporary directory\n   * @throws IOException if creating the directory fails\n   */\n  public Path newDirectory(Path parentDirectory) throws IOException {\n    Path path = Files.createTempDirectory(parentDirectory, null);\n    directories.add(path);\n    return path;\n  }\n\n  @Override\n  public void close() {\n    for (Path path : directories) {\n      if (Files.exists(path)) {\n        try {\n          MoreFiles.deleteRecursively(path, RecursiveDeleteOption.ALLOW_INSECURE);\n        } catch (IOException ignored) {\n          // ignored\n        }\n      }\n    }\n    directories.clear();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/filesystem/XdgDirectories.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.logging.Logger;\n\n/**\n * Obtains OS-specific directories based on the XDG Base Directory Specification.\n *\n * <p>Specifically, from the specification:\n *\n * <ul>\n *   <li>These directories are defined by the environment variables {@code $XDG_CACHE_HOME} and\n *       {@code $XDG_CONFIG_HOME}.\n *   <li>If {@code $XDG_CACHE_HOME} / {@code $XDG_CONFIG_HOME} is either not set or empty, a\n *       platform-specific equivalent of {@code $HOME/.cache} / {@code $HOME/.config} should be\n *       used.\n * </ul>\n *\n * @see <a\n *     href=\"https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\">https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html</a>\n */\npublic class XdgDirectories {\n\n  private static final Logger LOGGER = Logger.getLogger(XdgDirectories.class.getName());\n  private static final Path JIB_SUBDIRECTORY_LINUX =\n      Paths.get(\"google-cloud-tools-java\").resolve(\"jib\");\n  private static final Path JIB_SUBDIRECTORY_OTHER = Paths.get(\"Google\").resolve(\"Jib\");\n\n  public static Path getCacheHome() {\n    return getCacheHome(System.getProperties(), System.getenv());\n  }\n\n  public static Path getConfigHome() {\n    return getConfigHome(System.getProperties(), System.getenv());\n  }\n\n  /**\n   * Returns the default OS-specific cache directory.\n   *\n   * <p>For Linux, this is {@code $HOME/.cache/google-cloud-tools-java/jib/}.\n   *\n   * <p>For Windows, this is {@code %LOCALAPPDATA%\\Google\\Jib\\Cache\\}.\n   *\n   * <p>For macOS, this is {@code $HOME/Library/Caches/Google/Jib/}.\n   */\n  @VisibleForTesting\n  static Path getCacheHome(Properties properties, Map<String, String> environment) {\n    return getOsSpecificDirectory(\n        properties, environment, \"XDG_CACHE_HOME\", \".cache\", \"Cache\", \"Caches\");\n  }\n\n  /**\n   * Returns the default OS-specific config directory.\n   *\n   * <p>For Linux, this is {@code $HOME/.config/google-cloud-tools-java/jib/}.\n   *\n   * <p>For Windows, this is {@code %LOCALAPPDATA%\\Google\\Jib\\Config\\}.\n   *\n   * <p>For macOS, this is {@code $HOME/Library/Preferences/Google/Jib/}.\n   */\n  @VisibleForTesting\n  static Path getConfigHome(Properties properties, Map<String, String> environment) {\n    return getOsSpecificDirectory(\n        properties, environment, \"XDG_CONFIG_HOME\", \".config\", \"Config\", \"Preferences\");\n  }\n\n  /**\n   * Helper method for resolving directories on different operating systems.\n   *\n   * @param xdgEnvVariable the name of the environment variable used to resolve the XDG base\n   *     directory\n   * @param linuxFolder \".config\" or \".cache\"\n   * @param windowsFolder \"Config\" or \"Cache\"\n   * @param macFolder \"Preferences\" or \"Caches\"\n   * @return the full path constructed from the given parameters\n   */\n  private static Path getOsSpecificDirectory(\n      Properties properties,\n      Map<String, String> environment,\n      String xdgEnvVariable,\n      String linuxFolder,\n      String windowsFolder,\n      String macFolder) {\n\n    Path windowsSubDirectory = JIB_SUBDIRECTORY_OTHER.resolve(windowsFolder);\n    String rawOsName = properties.getProperty(\"os.name\");\n    String osName = rawOsName.toLowerCase(Locale.ENGLISH);\n    String xdgHome = environment.get(xdgEnvVariable);\n    String userHome = properties.getProperty(\"user.home\");\n    Path xdgPath = Paths.get(userHome, linuxFolder);\n\n    if (osName.contains(\"linux\")) {\n      // Use XDG environment variable if set and not empty.\n      if (xdgHome != null && !xdgHome.trim().isEmpty()) {\n        return Paths.get(xdgHome).resolve(JIB_SUBDIRECTORY_LINUX);\n      }\n      return xdgPath.resolve(JIB_SUBDIRECTORY_LINUX);\n\n    } else if (osName.contains(\"windows\")) {\n      // Use XDG environment variable if set and not empty.\n      if (xdgHome != null && !xdgHome.trim().isEmpty()) {\n        return Paths.get(xdgHome).resolve(windowsSubDirectory);\n      }\n\n      // Use %LOCALAPPDATA% for Windows.\n      String localAppDataEnv = environment.get(\"LOCALAPPDATA\");\n      if (localAppDataEnv == null || localAppDataEnv.trim().isEmpty()) {\n        LOGGER.warning(\"LOCALAPPDATA environment is invalid or missing\");\n        return xdgPath.resolve(windowsSubDirectory);\n      }\n      Path localAppData = Paths.get(localAppDataEnv);\n      if (!Files.exists(localAppData)) {\n        LOGGER.warning(() -> localAppData + \" does not exist\");\n        return xdgPath.resolve(windowsSubDirectory);\n      }\n      return localAppData.resolve(windowsSubDirectory);\n\n    } else if (osName.contains(\"mac\") || osName.contains(\"darwin\")) {\n      // Use XDG environment variable if set and not empty.\n      if (xdgHome != null && !xdgHome.trim().isEmpty()) {\n        return Paths.get(xdgHome).resolve(JIB_SUBDIRECTORY_OTHER);\n      }\n\n      // Use '~/Library/...' for macOS.\n      Path macDirectory = Paths.get(userHome, \"Library\", macFolder);\n      if (!Files.exists(macDirectory)) {\n        LOGGER.warning(() -> macDirectory + \" does not exist\");\n        return xdgPath.resolve(JIB_SUBDIRECTORY_OTHER);\n      }\n      return macDirectory.resolve(JIB_SUBDIRECTORY_OTHER);\n    }\n\n    throw new IllegalStateException(\"Unknown OS: \" + rawOsName);\n  }\n\n  private XdgDirectories() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.frontend;\n\nimport com.google.auth.oauth2.AccessToken;\nimport com.google.auth.oauth2.GoogleCredentials;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialHelperNotFoundException;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialHelperUnhandledServerUrlException;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport com.google.cloud.tools.jib.registry.credentials.DockerConfigCredentialRetriever;\nimport com.google.cloud.tools.jib.registry.credentials.DockerCredentialHelper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableMap;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\n/** Static factories for various {@link CredentialRetriever}s. */\npublic class CredentialRetrieverFactory {\n\n  /** Used for passing in mock {@link DockerCredentialHelper}s for testing. */\n  @VisibleForTesting\n  @FunctionalInterface\n  interface DockerCredentialHelperFactory {\n    DockerCredentialHelper create(\n        String registry, Path credentialHelper, Map<String, String> environment);\n  }\n\n  /** Used for passing in mock {@link GoogleCredentials} for testing. */\n  @VisibleForTesting\n  @FunctionalInterface\n  interface GoogleCredentialsProvider {\n    GoogleCredentials get() throws IOException;\n  }\n\n  // com.google.api.services.storage.StorageScopes.DEVSTORAGE_READ_WRITE\n  // OAuth2 credentials require at least the GCS write scope for GCR push. We need to manually set\n  // this scope for \"OAuth2 credentials\" instantiated from a service account, which are not scoped\n  // (i.e., createScopedRequired() returns true). Note that for a service account, the IAM roles of\n  // the service account determine the IAM permissions.\n  private static final String OAUTH_SCOPE_STORAGE_READ_WRITE =\n      \"https://www.googleapis.com/auth/devstorage.read_write\";\n\n  /** Mapping between well-known credential helpers and registries (suffixes). */\n  private static final ImmutableMap<String, String> WELL_KNOWN_CREDENTIAL_HELPERS =\n      ImmutableMap.of(\n          \"gcr.io\", \"docker-credential-gcr\", \"amazonaws.com\", \"docker-credential-ecr-login\");\n\n  /**\n   * Creates a new {@link CredentialRetrieverFactory} for an image.\n   *\n   * @param imageReference the image the credential are for\n   * @param logger a consumer for handling log events\n   * @return a new {@link CredentialRetrieverFactory}\n   */\n  public static CredentialRetrieverFactory forImage(\n      ImageReference imageReference, Consumer<LogEvent> logger) {\n    return new CredentialRetrieverFactory(\n        imageReference,\n        logger,\n        DockerCredentialHelper::new,\n        GoogleCredentials::getApplicationDefault,\n        Collections.emptyMap());\n  }\n\n  /**\n   * Creates a new {@link CredentialRetrieverFactory} for an image.\n   *\n   * @param imageReference the image the credential are for\n   * @param logger a consumer for handling log events\n   * @param environment environment variables for credential helper\n   * @return a new {@link CredentialRetrieverFactory}\n   */\n  public static CredentialRetrieverFactory forImage(\n      ImageReference imageReference, Consumer<LogEvent> logger, Map<String, String> environment) {\n    return new CredentialRetrieverFactory(\n        imageReference,\n        logger,\n        DockerCredentialHelper::new,\n        GoogleCredentials::getApplicationDefault,\n        environment);\n  }\n\n  private final ImageReference imageReference;\n  private final Consumer<LogEvent> logger;\n  private final DockerCredentialHelperFactory dockerCredentialHelperFactory;\n  private final GoogleCredentialsProvider googleCredentialsProvider;\n  private final Map<String, String> environment;\n\n  @VisibleForTesting\n  CredentialRetrieverFactory(\n      ImageReference imageReference,\n      Consumer<LogEvent> logger,\n      DockerCredentialHelperFactory dockerCredentialHelperFactory,\n      GoogleCredentialsProvider googleCredentialsProvider,\n      Map<String, String> environment) {\n    this.imageReference = imageReference;\n    this.logger = logger;\n    this.dockerCredentialHelperFactory = dockerCredentialHelperFactory;\n    this.googleCredentialsProvider = googleCredentialsProvider;\n    this.environment = environment;\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} that returns a known {@link Credential}.\n   *\n   * @param credential the known credential\n   * @param credentialSource the source of the credentials (for logging)\n   * @return a new {@link CredentialRetriever}\n   */\n  public CredentialRetriever known(Credential credential, String credentialSource) {\n    return () -> {\n      logGotCredentialsFrom(\"credentials from \" + credentialSource);\n      return Optional.of(credential);\n    };\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} for retrieving credentials via a Docker credential\n   * helper, such as {@code docker-credential-gcr}.\n   *\n   * @param credentialHelper the credential helper executable\n   * @return a new {@link CredentialRetriever}\n   */\n  public CredentialRetriever dockerCredentialHelper(String credentialHelper) {\n    return dockerCredentialHelper(Paths.get(credentialHelper));\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} for retrieving credentials via a Docker credential\n   * helper, such as {@code docker-credential-gcr}.\n   *\n   * @param credentialHelper the credential helper executable\n   * @return a new {@link CredentialRetriever}\n   * @see <a\n   *     href=\"https://github.com/docker/docker-credential-helpers#development\">https://github.com/docker/docker-credential-helpers#development</a>\n   */\n  public CredentialRetriever dockerCredentialHelper(Path credentialHelper) {\n    return () -> {\n      try {\n        return Optional.of(retrieveFromDockerCredentialHelper(credentialHelper));\n\n      } catch (CredentialHelperUnhandledServerUrlException ex) {\n        logger.accept(\n            LogEvent.info(\n                \"No credentials for \" + imageReference.getRegistry() + \" in \" + credentialHelper));\n        return Optional.empty();\n\n      } catch (IOException ex) {\n        throw new CredentialRetrievalException(ex);\n      }\n    };\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} that tries well-known Docker credential helpers to\n   * retrieve credentials based on the registry of the image, such as {@code docker-credential-gcr}\n   * for images with the registry ending with {@code gcr.io}.\n   *\n   * @return a new {@link CredentialRetriever}\n   */\n  public CredentialRetriever wellKnownCredentialHelpers() {\n    return () -> {\n      for (Map.Entry<String, String> entry : WELL_KNOWN_CREDENTIAL_HELPERS.entrySet()) {\n        try {\n          String registrySuffix = entry.getKey();\n          if (imageReference.getRegistry().endsWith(registrySuffix)) {\n            String credentialHelper = entry.getValue();\n            return Optional.of(retrieveFromDockerCredentialHelper(Paths.get(credentialHelper)));\n          }\n\n        } catch (CredentialHelperNotFoundException\n            | CredentialHelperUnhandledServerUrlException ex) {\n          if (ex.getMessage() != null) {\n            // Warns the user that the specified (or inferred) credential helper cannot be used.\n            logger.accept(LogEvent.info(ex.getMessage()));\n            if (ex.getCause() != null && ex.getCause().getMessage() != null) {\n              logger.accept(LogEvent.info(\"  Caused by: \" + ex.getCause().getMessage()));\n            }\n          }\n\n        } catch (IOException ex) {\n          throw new CredentialRetrievalException(ex);\n        }\n      }\n      return Optional.empty();\n    };\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} that tries to retrieve credentials from Docker config\n   * (located at {@code System.getProperty(\"user.home\")/.docker/config.json}).\n   *\n   * @return a new {@link CredentialRetriever}\n   * @see DockerConfigCredentialRetriever\n   */\n  public CredentialRetriever dockerConfig() {\n    return dockerConfig(\n        DockerConfigCredentialRetriever.create(\n            imageReference.getRegistry(),\n            Paths.get(System.getProperty(\"user.home\"), \".docker\", \"config.json\")));\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} that tries to retrieve credentials from a custom path\n   * to a Docker config.\n   *\n   * @param dockerConfigFile the path to the Docker config file\n   * @return a new {@link CredentialRetriever}\n   * @see DockerConfigCredentialRetriever\n   */\n  public CredentialRetriever dockerConfig(Path dockerConfigFile) {\n    return dockerConfig(\n        DockerConfigCredentialRetriever.create(imageReference.getRegistry(), dockerConfigFile));\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} that tries to retrieve credentials from a legacy\n   * Docker config file.\n   *\n   * @param dockerConfigFile the path to a legacy docker configuration file\n   * @return a new {@link CredentialRetriever}\n   * @see DockerConfigCredentialRetriever\n   */\n  public CredentialRetriever legacyDockerConfig(Path dockerConfigFile) {\n    return dockerConfig(\n        DockerConfigCredentialRetriever.createForLegacyFormat(\n            imageReference.getRegistry(), dockerConfigFile));\n  }\n\n  /**\n   * Creates a new {@link CredentialRetriever} that tries to retrieve credentials from <a\n   * href=\"https://cloud.google.com/docs/authentication/production\">Google Application Default\n   * Credentials</a>.\n   *\n   * @return a new {@link CredentialRetriever}\n   * @see <a\n   *     href=\"https://cloud.google.com/docs/authentication/production\">https://cloud.google.com/docs/authentication/production</a>\n   */\n  public CredentialRetriever googleApplicationDefaultCredentials() {\n    return () -> {\n      try {\n        if (imageReference.getRegistry().endsWith(\"gcr.io\")\n            || imageReference.getRegistry().endsWith(\"docker.pkg.dev\")) {\n          GoogleCredentials googleCredentials = googleCredentialsProvider.get();\n          logger.accept(LogEvent.info(\"Google ADC found\"));\n          if (googleCredentials.createScopedRequired()) { // not scoped if service account\n            // The short-lived OAuth2 access token to be generated from the service account with\n            // refreshIfExpired() below will have one-hour expiry (as of Aug 2019). Instead of using\n            // an access token, it is technically possible to use the service account private key to\n            // auth with GCR, but it does not worth writing complex code to achieve that.\n            logger.accept(LogEvent.info(\"ADC is a service account. Setting GCS read-write scope\"));\n            List<String> scope = Collections.singletonList(OAUTH_SCOPE_STORAGE_READ_WRITE);\n            googleCredentials = googleCredentials.createScoped(scope);\n          }\n          googleCredentials.refreshIfExpired();\n\n          logGotCredentialsFrom(\"Google Application Default Credentials\");\n          AccessToken accessToken = googleCredentials.getAccessToken();\n          // https://cloud.google.com/container-registry/docs/advanced-authentication#access_token\n          return Optional.of(Credential.from(\"oauth2accesstoken\", accessToken.getTokenValue()));\n        }\n\n      } catch (IOException ex) { // Includes the case where ADC is simply not available.\n        logger.accept(\n            LogEvent.info(\"ADC not present or error fetching access token: \" + ex.getMessage()));\n      }\n      return Optional.empty();\n    };\n  }\n\n  @VisibleForTesting\n  CredentialRetriever dockerConfig(\n      DockerConfigCredentialRetriever dockerConfigCredentialRetriever) {\n    return () -> {\n      Path configFile = dockerConfigCredentialRetriever.getDockerConfigFile();\n      try {\n        Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(logger);\n        if (credentials.isPresent()) {\n          logGotCredentialsFrom(\"credentials from Docker config (\" + configFile + \")\");\n          return credentials;\n        }\n\n      } catch (IOException ex) {\n        logger.accept(LogEvent.info(\"Unable to parse Docker config file: \" + configFile));\n      }\n      return Optional.empty();\n    };\n  }\n\n  private Credential retrieveFromDockerCredentialHelper(Path credentialHelper)\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    Credential credentials =\n        dockerCredentialHelperFactory\n            .create(imageReference.getRegistry(), credentialHelper, environment)\n            .retrieve();\n    logGotCredentialsFrom(\"credential helper \" + credentialHelper.getFileName().toString());\n    return credentials;\n  }\n\n  private void logGotCredentialsFrom(String credentialSource) {\n    logger.accept(LogEvent.lifecycle(\"Using \" + credentialSource + \" for \" + imageReference));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/global/JibSystemProperties.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.global;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.Range;\n\n/** Names of system properties defined/used by Jib. */\npublic class JibSystemProperties {\n\n  public static final String UPSTREAM_CLIENT = \"_JIB_UPSTREAM_CLIENT\";\n  private static final String DISABLE_USER_AGENT = \"_JIB_DISABLE_USER_AGENT\";\n\n  @VisibleForTesting public static final String HTTP_TIMEOUT = \"jib.httpTimeout\";\n\n  @VisibleForTesting static final String CROSS_REPOSITORY_BLOB_MOUNTS = \"jib.blobMounts\";\n\n  public static final String SEND_CREDENTIALS_OVER_HTTP = \"sendCredentialsOverHttp\";\n  public static final String SERIALIZE = \"jib.serialize\";\n\n  @VisibleForTesting public static final String SKIP_EXISTING_IMAGES = \"jib.skipExistingImages\";\n\n  /**\n   * Gets the HTTP connection/read timeouts for registry interactions in milliseconds. This is\n   * defined by the {@code jib.httpTimeout} system property. The default value is 20000 if the\n   * system property is not set, and 0 indicates an infinite timeout.\n   *\n   * @return the HTTP connection/read timeouts for registry interactions in milliseconds\n   */\n  public static int getHttpTimeout() {\n    if (Integer.getInteger(HTTP_TIMEOUT) == null) {\n      return 20000;\n    }\n    return Integer.getInteger(HTTP_TIMEOUT);\n  }\n\n  /**\n   * Gets whether or not to use <em>cross-repository blob mounts</em> when uploading image layers\n   * ({@code mount/from}). This is defined by the {@code jib.blobMounts} system property.\n   *\n   * @return {@code true} if {@code mount/from} should be used, {@code false} if not, defaulting to\n   *     {@code true}\n   */\n  public static boolean useCrossRepositoryBlobMounts() {\n    return System.getProperty(CROSS_REPOSITORY_BLOB_MOUNTS) == null\n        || Boolean.getBoolean(CROSS_REPOSITORY_BLOB_MOUNTS);\n  }\n\n  /**\n   * Gets whether or not to serialize Jib's execution. This is defined by the {@code jib.serialize}\n   * system property.\n   *\n   * @return {@code true} if Jib's execution should be serialized, {@code false} if not\n   */\n  public static boolean serializeExecution() {\n    return Boolean.getBoolean(SERIALIZE);\n  }\n\n  /**\n   * Gets whether or not to allow sending authentication information over insecure HTTP connections.\n   * This is defined by the {@code sendCredentialsOverHttp} system property.\n   *\n   * @return {@code true} if authentication information is allowed to be sent over insecure\n   *     connections, {@code false} if not\n   */\n  public static boolean sendCredentialsOverHttp() {\n    return Boolean.getBoolean(SEND_CREDENTIALS_OVER_HTTP);\n  }\n\n  /**\n   * Gets whether or not to enable the User-Agent header. This is defined by the {@code\n   * _JIB_DISABLE_USER_AGENT} system property.\n   *\n   * @return {@code true} if the User-Agent header is enabled, {@code false} if not\n   */\n  public static boolean isUserAgentEnabled() {\n    return Strings.isNullOrEmpty(System.getProperty(DISABLE_USER_AGENT));\n  }\n\n  /**\n   * Checks the {@code jib.httpTimeout} system property for invalid (non-integer or negative)\n   * values.\n   *\n   * @throws NumberFormatException if invalid values\n   */\n  public static void checkHttpTimeoutProperty() throws NumberFormatException {\n    checkNumericSystemProperty(HTTP_TIMEOUT, Range.atLeast(0));\n  }\n\n  /**\n   * Checks if {@code http.proxyPort} and {@code https.proxyPort} system properties are in the\n   * [0..65535] range when set.\n   *\n   * @throws NumberFormatException if invalid values\n   */\n  public static void checkProxyPortProperty() throws NumberFormatException {\n    checkNumericSystemProperty(\"http.proxyPort\", Range.closed(0, 65535));\n    checkNumericSystemProperty(\"https.proxyPort\", Range.closed(0, 65535));\n  }\n\n  /**\n   * Gets whether or not to skip pushing tags to existing images. This is defined by the {@code\n   * jib.skipExistingImages} system property.\n   *\n   * @return {@code true} if Jib should skip pushing tags to existing images, {@code false} if not\n   */\n  public static boolean skipExistingImages() {\n    return Boolean.getBoolean(SKIP_EXISTING_IMAGES);\n  }\n\n  private static void checkNumericSystemProperty(String property, Range<Integer> validRange) {\n    String value = System.getProperty(property);\n    if (value == null) {\n      return;\n    }\n\n    int parsed;\n    try {\n      parsed = Integer.parseInt(value);\n    } catch (NumberFormatException ex) {\n      throw new NumberFormatException(property + \" must be an integer: \" + value);\n    }\n    if (validRange.hasLowerBound() && validRange.lowerEndpoint() > parsed) {\n      throw new NumberFormatException(\n          property + \" cannot be less than \" + validRange.lowerEndpoint() + \": \" + value);\n    } else if (validRange.hasUpperBound() && validRange.upperEndpoint() < parsed) {\n      throw new NumberFormatException(\n          property + \" cannot be greater than \" + validRange.upperEndpoint() + \": \" + value);\n    }\n  }\n\n  private JibSystemProperties() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/hash/CountingDigestOutputStream.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.hash;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.security.DigestException;\nimport java.security.DigestOutputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/** A {@link DigestOutputStream} that also keeps track of the total number of bytes written. */\npublic class CountingDigestOutputStream extends DigestOutputStream {\n\n  private static final String SHA_256_ALGORITHM = \"SHA-256\";\n\n  private long bytesSoFar = 0;\n\n  /**\n   * Wraps the {@code outputStream}.\n   *\n   * @param outputStream the {@link OutputStream} to wrap.\n   */\n  public CountingDigestOutputStream(OutputStream outputStream) {\n    super(outputStream, null);\n    try {\n      setMessageDigest(MessageDigest.getInstance(SHA_256_ALGORITHM));\n    } catch (NoSuchAlgorithmException ex) {\n      throw new RuntimeException(\n          \"SHA-256 algorithm implementation not found - might be a broken JVM\");\n    }\n  }\n\n  /**\n   * Computes the hash and returns it along with the size of the bytes written to compute the hash.\n   * The buffer resets after this method is called, so this method should only be called once per\n   * computation.\n   *\n   * @return the computed hash and the size of the bytes consumed\n   */\n  public BlobDescriptor computeDigest() {\n    try {\n      byte[] hashedBytes = digest.digest();\n\n      // Encodes each hashed byte into 2-character hexadecimal representation.\n      StringBuilder stringBuilder = new StringBuilder(2 * hashedBytes.length);\n      for (byte b : hashedBytes) {\n        stringBuilder.append(String.format(\"%02x\", b));\n      }\n      String hash = stringBuilder.toString();\n\n      BlobDescriptor blobDescriptor =\n          new BlobDescriptor(bytesSoFar, DescriptorDigest.fromHash(hash));\n      bytesSoFar = 0;\n      return blobDescriptor;\n\n    } catch (DigestException ex) {\n      throw new RuntimeException(\"SHA-256 algorithm produced invalid hash: \" + ex.getMessage(), ex);\n    }\n  }\n\n  @Override\n  public void write(byte[] data, int offset, int length) throws IOException {\n    super.write(data, offset, length);\n    bytesSoFar += length;\n  }\n\n  @Override\n  public void write(int singleByte) throws IOException {\n    super.write(singleByte);\n    bytesSoFar++;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/hash/Digests.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.hash;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.ByteStreams;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.List;\n\n/**\n * Utility class for computing a digest for various inputs while optionally writing to an output\n * stream.\n */\n// Note: intentionally this class does not depend on Blob, as Blob classes depend on this class.\n// TODO: BlobDescriptor is merely a tuple of (size, digest). Rename BlobDescriptor to something\n// more general.\npublic class Digests {\n\n  private Digests() {}\n\n  public static DescriptorDigest computeJsonDigest(JsonTemplate template) throws IOException {\n    return computeDigest(template, ByteStreams.nullOutputStream()).getDigest();\n  }\n\n  public static DescriptorDigest computeJsonDigest(List<? extends JsonTemplate> templates)\n      throws IOException {\n    WritableContents contents = contentsOut -> JsonTemplateMapper.writeTo(templates, contentsOut);\n    return computeDigest(contents, ByteStreams.nullOutputStream()).getDigest();\n  }\n\n  public static BlobDescriptor computeDigest(JsonTemplate template) throws IOException {\n    return computeDigest(template, ByteStreams.nullOutputStream());\n  }\n\n  public static BlobDescriptor computeDigest(JsonTemplate template, OutputStream outStream)\n      throws IOException {\n    WritableContents contents = contentsOut -> JsonTemplateMapper.writeTo(template, contentsOut);\n    return computeDigest(contents, outStream);\n  }\n\n  public static BlobDescriptor computeDigest(InputStream inStream) throws IOException {\n    return computeDigest(inStream, ByteStreams.nullOutputStream());\n  }\n\n  /**\n   * Computes the digest by consuming the contents.\n   *\n   * @param contents the contents for which the digest is computed\n   * @return computed digest and bytes consumed\n   * @throws IOException if reading fails\n   */\n  public static BlobDescriptor computeDigest(WritableContents contents) throws IOException {\n    return computeDigest(contents, ByteStreams.nullOutputStream());\n  }\n\n  /**\n   * Computes the digest by consuming the contents of an {@link InputStream} while copying it to an\n   * {@link OutputStream}. Returns the computed digest along with the size of the bytes consumed to\n   * compute the digest. Does not close either stream.\n   *\n   * @param inStream the stream to read the contents from\n   * @param outStream the stream to which the contents are copied\n   * @return computed digest and bytes consumed\n   * @throws IOException if reading from or writing fails\n   */\n  public static BlobDescriptor computeDigest(InputStream inStream, OutputStream outStream)\n      throws IOException {\n    WritableContents contents = contentsOut -> ByteStreams.copy(inStream, contentsOut);\n    return computeDigest(contents, outStream);\n  }\n\n  /**\n   * Computes the digest by consuming the contents while copying it to an {@link OutputStream}.\n   * Returns the computed digest along with the size of the bytes consumed to compute the digest.\n   * Does not close the stream.\n   *\n   * @param contents the contents to compute digest for\n   * @param outStream the stream to which the contents are copied\n   * @return computed digest and bytes consumed\n   * @throws IOException if reading from or writing fails\n   */\n  public static BlobDescriptor computeDigest(WritableContents contents, OutputStream outStream)\n      throws IOException {\n    CountingDigestOutputStream digestOutStream = new CountingDigestOutputStream(outStream);\n    contents.writeTo(digestOutStream);\n    digestOutStream.flush();\n    return digestOutStream.computeDigest();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/hash/WritableContents.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.hash;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * As a function, writes some contents to an output stream. As a class, represents contents that can\n * be written to an output stream. This may be \"unrealized-before-write\" contents; for example, a\n * file may be open and read for input contents only when this function is called to write to an\n * output stream.\n */\n@FunctionalInterface\npublic interface WritableContents {\n\n  void writeTo(OutputStream outputStream) throws IOException;\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/Authorization.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Objects;\n\n/**\n * Holds the credentials for an HTTP {@code Authorization} header.\n *\n * <p>The HTTP {@code Authorization} header is in the format:\n *\n * <pre>{@code Authorization: <scheme> <token>}</pre>\n */\npublic class Authorization {\n\n  /**\n   * Create an authentication from basic credentials.\n   *\n   * @param username the username\n   * @param secret the secret\n   * @return an {@link Authorization} with a {@code Basic} credentials\n   */\n  public static Authorization fromBasicCredentials(String username, String secret) {\n    String credentials = username + \":\" + secret;\n    String token = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8));\n    return new Authorization(\"Basic\", token);\n  }\n\n  /**\n   * Create an authentication from bearer token.\n   *\n   * @param token the token\n   * @return an {@link Authorization} with a {@code Bearer} token\n   */\n  public static Authorization fromBearerToken(String token) {\n    return new Authorization(\"Bearer\", token);\n  }\n\n  private final String scheme;\n  private final String token;\n\n  private Authorization(String scheme, String token) {\n    this.scheme = scheme;\n    this.token = token;\n  }\n\n  public String getScheme() {\n    return scheme;\n  }\n\n  public String getToken() {\n    return token;\n  }\n\n  /** Return the HTTP {@link Authorization} header value. */\n  @Override\n  public String toString() {\n    return scheme + \" \" + token;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof Authorization)) {\n      return false;\n    }\n    Authorization otherAuthorization = (Authorization) other;\n    return scheme.equals(otherAuthorization.scheme) && token.equals(otherAuthorization.token);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(scheme, token);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/BlobHttpContent.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.api.client.http.HttpContent;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.function.Consumer;\n\n/** {@link Blob}-backed {@link HttpContent}. */\npublic class BlobHttpContent implements HttpContent {\n\n  private final Blob blob;\n  private final String contentType;\n  private final Consumer<Long> writtenByteCountListener;\n\n  public BlobHttpContent(Blob blob, String contentType) {\n    this(blob, contentType, ignored -> {});\n  }\n\n  /**\n   * Create a new BlobHttpClient.\n   *\n   * @param blob a blob to wrap\n   * @param contentType the http contentType descriptor\n   * @param writtenByteCountListener to listen for written byte feedback\n   */\n  public BlobHttpContent(Blob blob, String contentType, Consumer<Long> writtenByteCountListener) {\n    this.blob = blob;\n    this.contentType = contentType;\n    this.writtenByteCountListener = writtenByteCountListener;\n  }\n\n  @Override\n  public long getLength() {\n    // Returns negative value for unknown length.\n    return -1;\n  }\n\n  @Override\n  public String getType() {\n    return contentType;\n  }\n\n  @Override\n  public boolean retrySupported() {\n    return blob.isRetryable();\n  }\n\n  @Override\n  public void writeTo(OutputStream outputStream) throws IOException {\n    blob.writeTo(new NotifyingOutputStream(outputStream, writtenByteCountListener));\n    outputStream.flush();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/FailoverHttpClient.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.api.client.http.GenericUrl;\nimport com.google.api.client.http.HttpBackOffIOExceptionHandler;\nimport com.google.api.client.http.HttpHeaders;\nimport com.google.api.client.http.HttpIOExceptionHandler;\nimport com.google.api.client.http.HttpMethods;\nimport com.google.api.client.http.HttpRequest;\nimport com.google.api.client.http.HttpResponseException;\nimport com.google.api.client.http.HttpTransport;\nimport com.google.api.client.http.apache.v2.ApacheHttpTransport;\nimport com.google.api.client.util.ExponentialBackOff;\nimport com.google.api.client.util.SslUtils;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport java.io.IOException;\nimport java.net.ConnectException;\nimport java.net.URL;\nimport java.security.GeneralSecurityException;\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Consumer;\nimport java.util.function.Supplier;\nimport javax.net.ssl.SSLException;\nimport org.apache.http.conn.ssl.NoopHostnameVerifier;\nimport org.apache.http.conn.ssl.SSLConnectionSocketFactory;\nimport org.apache.http.impl.client.HttpClientBuilder;\n\n/**\n * Thread-safe HTTP client that can automatically failover from secure HTTPS to insecure HTTPS or\n * HTTP. Intended to be created once and shared to be called at multiple places. Callers should\n * close the returned {@link Response}.\n *\n * <p>The failover (if enabled) in the following way:\n *\n * <ul>\n *   <li>When a port is provided (for example {@code my-registry:5000/my-repo}):\n *       <ol>\n *         <li>Attempts secure HTTPS on the specified port.\n *         <li>If (1) fails due to {@link SSLException}, re-attempts secure HTTPS on the specified\n *             port but disabling certificate validation.\n *         <li>If (2) fails again due to {@link SSLException}, attempts plain-HTTP on the specified\n *             port.\n *       </ol>\n *   <li>When a port is not provided (for example {@code my-registry/my-repo}):\n *       <ol>\n *         <li>Attempts secure HTTPS on port 443 (default HTTPS port).\n *         <li>If (1) fails due to {@link SSLException}, re-attempts secure HTTPS on port 443 but\n *             disabling certificate validation.\n *         <li>If (2) fails again due to {@link SSLException}, attempts plain-HTTP on port 80\n *             (default HTTP port).\n *         <li>Or, if (1) fails due to non-timeout {@link ConnectException}, attempts plain-HTTP on\n *             port 80.\n *       </ol>\n * </ul>\n *\n * <p>This failover behavior is similar to how the Docker client works:\n * https://docs.docker.com/registry/insecure/#deploy-a-plain-http-registry\n */\npublic class FailoverHttpClient {\n\n  /** Represents failover actions taken. To be recorded in the failover history. */\n  private static enum Failover {\n    NONE, // no failover (secure HTTPS)\n    INSECURE_HTTPS, // HTTPS with certificate validation disabled\n    HTTP // plain HTTP\n  }\n\n  private static boolean isHttpsProtocol(URL url) {\n    return \"https\".equals(url.getProtocol());\n  }\n\n  private static URL toHttp(URL url) {\n    GenericUrl httpUrl = new GenericUrl(url);\n    httpUrl.setScheme(\"http\");\n    return httpUrl.toURL();\n  }\n\n  private static HttpTransport getSecureHttpTransport() {\n    // Do not use NetHttpTransport. It does not process response errors properly.\n    // See https://github.com/google/google-http-java-client/issues/39\n    //\n    // A new ApacheHttpTransport needs to be created for each connection because otherwise HTTP\n    // connection persistence causes the connection to throw NoHttpResponseException.\n    HttpClientBuilder httpClientBuilder =\n        ApacheHttpTransport.newDefaultHttpClientBuilder()\n            // using \"system socket factory\" to enable sending client certificate\n            // https://github.com/GoogleContainerTools/jib/issues/2585\n            .setSSLSocketFactory(SSLConnectionSocketFactory.getSystemSocketFactory());\n    return new ApacheHttpTransport(httpClientBuilder.build());\n  }\n\n  private static HttpTransport getInsecureHttpTransport() {\n    try {\n      HttpClientBuilder httpClientBuilder =\n          ApacheHttpTransport.newDefaultHttpClientBuilder()\n              .setSSLSocketFactory(null) // creates new factory with the SSLContext given below\n              .setSSLContext(SslUtils.trustAllSSLContext())\n              .setSSLHostnameVerifier(new NoopHostnameVerifier());\n      // Do not use NetHttpTransport. See comments in getConnectionFactory for details.\n      return new ApacheHttpTransport(httpClientBuilder.build());\n    } catch (GeneralSecurityException ex) {\n      throw new RuntimeException(\"platform does not support TLS protocol\", ex);\n    }\n  }\n\n  private final boolean enableHttpAndInsecureFailover;\n  private final boolean sendAuthorizationOverHttp;\n  private final Consumer<LogEvent> logger;\n  private final Supplier<HttpTransport> secureHttpTransportFactory;\n  private final Supplier<HttpTransport> insecureHttpTransportFactory;\n\n  private final ConcurrentHashMap<String, Failover> failoverHistory = new ConcurrentHashMap<>();\n\n  private final Deque<HttpTransport> transportsCreated = new ArrayDeque<>();\n  private final Deque<Response> responsesCreated = new ArrayDeque<>();\n  private final boolean enableRetry;\n\n  /**\n   * Create a new FailoverHttpclient.\n   *\n   * @param enableHttpAndInsecureFailover to enable automatic failover to insecure connection types\n   * @param sendAuthorizationOverHttp allow sending auth over http connections\n   * @param logger to receive log events\n   */\n  public FailoverHttpClient(\n      boolean enableHttpAndInsecureFailover,\n      boolean sendAuthorizationOverHttp,\n      Consumer<LogEvent> logger) {\n    this(enableHttpAndInsecureFailover, sendAuthorizationOverHttp, logger, true);\n  }\n\n  @VisibleForTesting\n  FailoverHttpClient(\n      boolean enableHttpAndInsecureFailover,\n      boolean sendAuthorizationOverHttp,\n      Consumer<LogEvent> logger,\n      boolean enableRetry) {\n    this(\n        enableHttpAndInsecureFailover,\n        sendAuthorizationOverHttp,\n        logger,\n        FailoverHttpClient::getSecureHttpTransport,\n        FailoverHttpClient::getInsecureHttpTransport,\n        enableRetry);\n  }\n\n  @VisibleForTesting\n  FailoverHttpClient(\n      boolean enableHttpAndInsecureFailover,\n      boolean sendAuthorizationOverHttp,\n      Consumer<LogEvent> logger,\n      Supplier<HttpTransport> secureHttpTransportFactory,\n      Supplier<HttpTransport> insecureHttpTransportFactory,\n      boolean enableRetry) {\n    this.enableHttpAndInsecureFailover = enableHttpAndInsecureFailover;\n    this.sendAuthorizationOverHttp = sendAuthorizationOverHttp;\n    this.logger = logger;\n    this.secureHttpTransportFactory = secureHttpTransportFactory;\n    this.insecureHttpTransportFactory = insecureHttpTransportFactory;\n    this.enableRetry = enableRetry;\n  }\n\n  /**\n   * Closes all connections and allocated resources, whether they are currently used or not.\n   *\n   * <p>If an I/O error occurs, shutdown attempts stop immediately, resulting in partial resource\n   * release up to that point. The method can be called again later to re-attempt releasing all\n   * resources.\n   *\n   * @throws IOException when I/O error shutting down resources\n   */\n  public void shutDown() throws IOException {\n    synchronized (transportsCreated) {\n      while (!transportsCreated.isEmpty()) {\n        transportsCreated.peekFirst().shutdown();\n        transportsCreated.removeFirst();\n      }\n    }\n    synchronized (responsesCreated) {\n      while (!responsesCreated.isEmpty()) {\n        responsesCreated.peekFirst().close();\n        responsesCreated.removeFirst();\n      }\n    }\n  }\n\n  /**\n   * Sends the request with method GET.\n   *\n   * @param url endpoint URL\n   * @param request the request to send\n   * @return the response to the sent request\n   * @throws IOException if sending the request fails\n   */\n  public Response get(URL url, Request request) throws IOException {\n    return call(HttpMethods.GET, url, request);\n  }\n\n  /**\n   * Sends the request with method POST.\n   *\n   * @param url endpoint URL\n   * @param request the request to send\n   * @return the response to the sent request\n   * @throws IOException if sending the request fails\n   */\n  public Response post(URL url, Request request) throws IOException {\n    return call(HttpMethods.POST, url, request);\n  }\n\n  /**\n   * Sends the request with method PUT.\n   *\n   * @param url endpoint URL\n   * @param request the request to send\n   * @return the response to the sent request\n   * @throws IOException if sending the request fails\n   */\n  public Response put(URL url, Request request) throws IOException {\n    return call(HttpMethods.PUT, url, request);\n  }\n\n  /**\n   * Sends the request.\n   *\n   * @param httpMethod the HTTP request method\n   * @param url endpoint URL\n   * @param request the request to send\n   * @return the response to the sent request\n   * @throws IOException if building the HTTP request fails.\n   */\n  public Response call(String httpMethod, URL url, Request request) throws IOException {\n    if (!isHttpsProtocol(url)) {\n      if (enableHttpAndInsecureFailover) { // HTTP requested. We only care if HTTP is enabled.\n        return call(httpMethod, url, request, getHttpTransport(true), true);\n      }\n      throw new SSLException(\"insecure HTTP connection not allowed: \" + url);\n    }\n\n    Optional<Response> fastPathResponse = followFailoverHistory(httpMethod, url, request);\n    if (fastPathResponse.isPresent()) {\n      return fastPathResponse.get();\n    }\n\n    try {\n      return call(httpMethod, url, request, getHttpTransport(true), !enableHttpAndInsecureFailover);\n\n    } catch (SSLException ex) {\n      if (!enableHttpAndInsecureFailover) {\n        throw ex;\n      }\n\n      try {\n        logInsecureHttpsFailover(url);\n        Response response = call(httpMethod, url, request, getHttpTransport(false), false);\n        failoverHistory.put(url.getHost() + \":\" + url.getPort(), Failover.INSECURE_HTTPS);\n        return response;\n\n      } catch (SSLException ignored) { // This is usually when the server is plain-HTTP.\n        logHttpFailover(url);\n        Response response = call(httpMethod, toHttp(url), request, getHttpTransport(true), true);\n        failoverHistory.put(url.getHost() + \":\" + url.getPort(), Failover.HTTP);\n        return response;\n      }\n\n    } catch (ConnectException ex) {\n      // It is observed that Open/Oracle JDKs sometimes throw SocketTimeoutException but other times\n      // ConnectException for connection timeout. (Could be a JDK bug.) Note SocketTimeoutException\n      // does not extend ConnectException (or vice versa), and we want to be consistent to error out\n      // on timeouts: https://github.com/GoogleContainerTools/jib/issues/1895#issuecomment-527544094\n      if (ex.getMessage() == null || !ex.getMessage().contains(\"timed out\")) {\n        // Fall back to HTTP only if \"url\" had no port specified (i.e., we tried the default HTTPS\n        // port 443) and we could not connect to 443. It's worth trying port 80.\n        if (enableHttpAndInsecureFailover && isHttpsProtocol(url) && url.getPort() == -1) {\n          logHttpFailover(url);\n          Response response = call(httpMethod, toHttp(url), request, getHttpTransport(true), true);\n          failoverHistory.put(url.getHost() + \":\" + url.getPort(), Failover.HTTP);\n          return response;\n        }\n      }\n      throw ex;\n    }\n  }\n\n  private Optional<Response> followFailoverHistory(String httpMethod, URL url, Request request)\n      throws IOException {\n    Preconditions.checkArgument(isHttpsProtocol(url));\n    switch (failoverHistory.getOrDefault(url.getHost() + \":\" + url.getPort(), Failover.NONE)) {\n      case HTTP:\n        return Optional.of(call(httpMethod, toHttp(url), request, getHttpTransport(true), true));\n      case INSECURE_HTTPS:\n        return Optional.of(call(httpMethod, url, request, getHttpTransport(false), true));\n      default:\n        return Optional.empty(); // No history found. Should go for normal execution path.\n    }\n  }\n\n  // TODO: remove retryOnIoException and turn on/off retry based on whether it's an SSLException or\n  // not: https://github.com/GoogleContainerTools/jib/issues/3422\n  private Response call(\n      String httpMethod,\n      URL url,\n      Request request,\n      HttpTransport httpTransport,\n      boolean retryOnIoException) // See https://github.com/GoogleContainerTools/jib/issues/3424\n      throws IOException {\n    boolean clearAuthorization = !isHttpsProtocol(url) && !sendAuthorizationOverHttp;\n\n    HttpHeaders requestHeaders =\n        clearAuthorization\n            ? request.getHeaders().clone().setAuthorization((String) null) // deep clone implemented\n            : request.getHeaders();\n\n    HttpRequest httpRequest =\n        httpTransport\n            .createRequestFactory()\n            .buildRequest(httpMethod, new GenericUrl(url), request.getHttpContent())\n            .setUseRawRedirectUrls(true)\n            .setHeaders(requestHeaders);\n    if (enableRetry && retryOnIoException) {\n      httpRequest.setIOExceptionHandler(createBackOffRetryHandler());\n    }\n    if (request.getHttpTimeout() != null) {\n      httpRequest.setConnectTimeout(request.getHttpTimeout());\n      httpRequest.setReadTimeout(request.getHttpTimeout());\n    }\n\n    try {\n      Response response = new Response(httpRequest.execute());\n      synchronized (responsesCreated) {\n        responsesCreated.add(response);\n      }\n      return response;\n    } catch (HttpResponseException ex) {\n      throw new ResponseException(ex, clearAuthorization);\n    }\n  }\n\n  private HttpIOExceptionHandler createBackOffRetryHandler() {\n    return new HttpBackOffIOExceptionHandler(new ExponentialBackOff()) {\n      @Override\n      public boolean handleIOException(HttpRequest request, boolean supportsRetry)\n          throws IOException {\n        String requestUrl = request.getRequestMethod() + \" \" + request.getUrl();\n        if (super.handleIOException(request, supportsRetry)) {\n          logger.accept(LogEvent.warn(requestUrl + \" failed and will be retried\"));\n          return true;\n        }\n        logger.accept(LogEvent.warn(requestUrl + \" failed and will NOT be retried\"));\n        return false;\n      }\n    };\n  }\n\n  private HttpTransport getHttpTransport(boolean secureTransport) {\n    HttpTransport transport =\n        secureTransport ? secureHttpTransportFactory.get() : insecureHttpTransportFactory.get();\n    synchronized (transportsCreated) {\n      transportsCreated.add(transport);\n    }\n    return transport;\n  }\n\n  private void logHttpFailover(URL url) {\n    String log = \"Failed to connect to \" + url + \" over HTTPS. Attempting again with HTTP.\";\n    logger.accept(LogEvent.warn(log));\n  }\n\n  private void logInsecureHttpsFailover(URL url) {\n    String log = \"Cannot verify server at \" + url + \". Attempting again with no TLS verification.\";\n    logger.accept(LogEvent.warn(log));\n  }\n\n  @VisibleForTesting\n  public Deque<HttpTransport> getTransportsCreated() {\n    return transportsCreated;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/NotifyingOutputStream.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.function.Consumer;\n\n/** Counts the number of bytes written and reports the count to a callback. */\npublic class NotifyingOutputStream extends OutputStream {\n\n  /** The underlying {@link OutputStream} to wrap and forward bytes to. */\n  private final OutputStream underlyingOutputStream;\n\n  /** Receives a count of bytes written since the last call. */\n  private final Consumer<Long> byteCountListener;\n\n  /** Number of bytes to provide to {@link #byteCountListener}. */\n  private long byteCount = 0;\n\n  /**\n   * Wraps the {@code underlyingOutputStream} to count the bytes written.\n   *\n   * @param underlyingOutputStream the wrapped {@link OutputStream}\n   * @param byteCountListener the byte count {@link Consumer}\n   */\n  public NotifyingOutputStream(\n      OutputStream underlyingOutputStream, Consumer<Long> byteCountListener) {\n    this.underlyingOutputStream = underlyingOutputStream;\n    this.byteCountListener = byteCountListener;\n  }\n\n  @Override\n  public void write(int singleByte) throws IOException {\n    underlyingOutputStream.write(singleByte);\n    countAndCallListener(1);\n  }\n\n  @Override\n  public void write(byte[] byteArray) throws IOException {\n    underlyingOutputStream.write(byteArray);\n    countAndCallListener(byteArray.length);\n  }\n\n  @Override\n  public void write(byte[] byteArray, int offset, int length) throws IOException {\n    underlyingOutputStream.write(byteArray, offset, length);\n    countAndCallListener(length);\n  }\n\n  @Override\n  public void flush() throws IOException {\n    underlyingOutputStream.flush();\n    countAndCallListener(0);\n  }\n\n  @Override\n  public void close() throws IOException {\n    underlyingOutputStream.close();\n    countAndCallListener(0);\n  }\n\n  private void countAndCallListener(int written) {\n    this.byteCount += written;\n    if (byteCount == 0) {\n      return;\n    }\n\n    byteCountListener.accept(byteCount);\n    byteCount = 0;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/Request.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.api.client.http.HttpContent;\nimport com.google.api.client.http.HttpHeaders;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** Holds an HTTP request. */\npublic class Request {\n\n  /** The HTTP request headers. */\n  private final HttpHeaders headers;\n\n  /** The HTTP request body. */\n  @Nullable private final HttpContent body;\n\n  /** HTTP connection and read timeout. */\n  @Nullable private final Integer httpTimeout;\n\n  public static class Builder {\n\n    private final HttpHeaders headers = new HttpHeaders().setAccept(\"*/*\");\n    @Nullable private HttpContent body;\n    @Nullable private Integer httpTimeout;\n\n    public Request build() {\n      return new Request(this);\n    }\n\n    /**\n     * Sets the {@code Authorization} header.\n     *\n     * @param authorization the authorization\n     * @return this\n     */\n    public Builder setAuthorization(@Nullable Authorization authorization) {\n      headers.setAuthorization(authorization == null ? null : authorization.toString());\n      return this;\n    }\n\n    /**\n     * Sets the {@code Accept} header.\n     *\n     * @param mimeTypes the items to pass into the accept header\n     * @return this\n     */\n    public Builder setAccept(List<String> mimeTypes) {\n      headers.setAccept(String.join(\",\", mimeTypes));\n      return this;\n    }\n\n    /**\n     * Sets the {@code User-Agent} header.\n     *\n     * @param userAgent the user agent\n     * @return this\n     */\n    public Builder setUserAgent(@Nullable String userAgent) {\n      headers.setUserAgent(userAgent);\n      return this;\n    }\n\n    /**\n     * Sets the HTTP connection and read timeout in milliseconds. {@code null} uses the default\n     * timeout and {@code 0} an infinite timeout.\n     *\n     * @param httpTimeout timeout in milliseconds\n     * @return this\n     */\n    public Builder setHttpTimeout(@Nullable Integer httpTimeout) {\n      this.httpTimeout = httpTimeout;\n      return this;\n    }\n\n    /**\n     * Sets the body and its corresponding {@code Content-Type} header.\n     *\n     * @param httpContent the body content\n     * @return this\n     */\n    public Builder setBody(@Nullable HttpContent httpContent) {\n      this.body = httpContent;\n      return this;\n    }\n  }\n\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  private Request(Builder builder) {\n    this.headers = builder.headers;\n    this.body = builder.body;\n    this.httpTimeout = builder.httpTimeout;\n  }\n\n  HttpHeaders getHeaders() {\n    return headers;\n  }\n\n  @Nullable\n  HttpContent getHttpContent() {\n    return body;\n  }\n\n  @Nullable\n  Integer getHttpTimeout() {\n    return httpTimeout;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/Response.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.api.client.http.GenericUrl;\nimport com.google.api.client.http.HttpResponse;\nimport com.google.common.net.HttpHeaders;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\n\n/** Holds an HTTP response. */\npublic class Response implements Closeable {\n\n  private final HttpResponse httpResponse;\n\n  Response(HttpResponse httpResponse) {\n    this.httpResponse = httpResponse;\n  }\n\n  /**\n   * Returns the HTTP status code of the response.\n   *\n   * @return the HTTP status code of the response\n   */\n  public int getStatusCode() {\n    return httpResponse.getStatusCode();\n  }\n\n  /**\n   * Returns a list of the header string values for the given header name.\n   *\n   * @param headerName the header name\n   * @return a list of headers in the response\n   */\n  public List<String> getHeader(String headerName) {\n    return httpResponse.getHeaders().getHeaderStringValues(headerName);\n  }\n\n  /**\n   * Returns the content length from the header.\n   *\n   * @return the first {@code Content-Length} header, or {@code -1} if not found\n   * @throws NumberFormatException if parsing the content length header fails\n   */\n  public long getContentLength() throws NumberFormatException {\n    String contentLengthHeader =\n        httpResponse.getHeaders().getFirstHeaderStringValue(HttpHeaders.CONTENT_LENGTH);\n    if (contentLengthHeader == null) {\n      return -1;\n    }\n    try {\n      return Long.parseLong(contentLengthHeader);\n\n    } catch (NumberFormatException ex) {\n      return -1;\n    }\n  }\n\n  /**\n   * Returns the content of the HTTP response.\n   *\n   * @return the HTTP response body as an {@link InputStream}.\n   * @throws IOException if getting the HTTP response content fails.\n   */\n  public InputStream getBody() throws IOException {\n    return httpResponse.getContent();\n  }\n\n  /**\n   * Returns the original request URL.\n   *\n   * @return the original request URL\n   */\n  public GenericUrl getRequestUrl() {\n    return httpResponse.getRequest().getUrl();\n  }\n\n  @Override\n  public void close() throws IOException {\n    httpResponse.disconnect();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/http/ResponseException.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.api.client.http.HttpHeaders;\nimport com.google.api.client.http.HttpResponseException;\nimport java.io.IOException;\n\n/** Holds an HTTP response exception. */\npublic class ResponseException extends IOException {\n\n  private final HttpResponseException httpResponseException;\n  private final boolean requestAuthorizationCleared;\n\n  ResponseException(\n      HttpResponseException httpResponseException, boolean requestAuthorizationCleared) {\n    super(httpResponseException.getMessage(), httpResponseException);\n    this.httpResponseException = httpResponseException;\n    this.requestAuthorizationCleared = requestAuthorizationCleared;\n  }\n\n  public int getStatusCode() {\n    return httpResponseException.getStatusCode();\n  }\n\n  public String getContent() {\n    return httpResponseException.getContent();\n  }\n\n  public HttpHeaders getHeaders() {\n    return httpResponseException.getHeaders();\n  }\n\n  /**\n   * Returns whether the {@code Authorization} HTTP header was cleared (and thus not sent).\n   *\n   * @return whether the {@code Authorization} HTTP header was cleared\n   */\n  public boolean requestAuthorizationCleared() {\n    return requestAuthorizationCleared;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/DigestOnlyLayer.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\n\n/** A {@link Layer} reference that only has its {@link DescriptorDigest}. */\npublic class DigestOnlyLayer implements Layer {\n\n  /** The {@link BlobDescriptor} of the compressed layer content. */\n  private final BlobDescriptor blobDescriptor;\n\n  /**\n   * Instantiate with a {@link DescriptorDigest}.\n   *\n   * @param digest the digest to instantiate the {@link DigestOnlyLayer} from\n   */\n  public DigestOnlyLayer(DescriptorDigest digest) {\n    blobDescriptor = new BlobDescriptor(digest);\n  }\n\n  @Override\n  public Blob getBlob() throws LayerPropertyNotFoundException {\n    throw new LayerPropertyNotFoundException(\"Blob not available for digest-only layer\");\n  }\n\n  @Override\n  public BlobDescriptor getBlobDescriptor() {\n    return blobDescriptor;\n  }\n\n  @Override\n  public DescriptorDigest getDiffId() throws LayerPropertyNotFoundException {\n    throw new LayerPropertyNotFoundException(\"Diff ID not available for digest-only layer\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/Image.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.configuration.DockerHealthCheck;\nimport com.google.cloud.tools.jib.image.json.HistoryEntry;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.time.Instant;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.annotation.Nullable;\n\n/** Represents an image. */\npublic class Image {\n\n  /** Builds the immutable {@link Image}. */\n  public static class Builder {\n\n    private final Class<? extends ManifestTemplate> imageFormat;\n    private final ImmutableList.Builder<Layer> imageLayersBuilder = ImmutableList.builder();\n    private final ImmutableList.Builder<HistoryEntry> historyBuilder = ImmutableList.builder();\n\n    // Don't use ImmutableMap.Builder because it does not allow for replacing existing keys with new\n    // values.\n    private final Map<String, String> environmentBuilder = new HashMap<>();\n    private final Map<String, String> labelsBuilder = new HashMap<>();\n    private final Set<Port> exposedPortsBuilder = new HashSet<>();\n    private final Set<AbsoluteUnixPath> volumesBuilder = new HashSet<>();\n\n    @Nullable private Instant created;\n    private String architecture = \"amd64\";\n    private String os = \"linux\";\n    @Nullable private ImmutableList<String> entrypoint;\n    @Nullable private ImmutableList<String> programArguments;\n    @Nullable private DockerHealthCheck healthCheck;\n    @Nullable private String workingDirectory;\n    @Nullable private String user;\n\n    private Builder(Class<? extends ManifestTemplate> imageFormat) {\n      this.imageFormat = imageFormat;\n    }\n\n    /**\n     * Sets the image creation time.\n     *\n     * @param created the creation time\n     * @return this\n     */\n    public Builder setCreated(Instant created) {\n      this.created = created;\n      return this;\n    }\n\n    /**\n     * Sets the image architecture.\n     *\n     * @param architecture the architecture\n     * @return this\n     */\n    public Builder setArchitecture(String architecture) {\n      this.architecture = architecture;\n      return this;\n    }\n\n    /**\n     * Sets the image operating system.\n     *\n     * @param os the operating system\n     * @return this\n     */\n    public Builder setOs(String os) {\n      this.os = os;\n      return this;\n    }\n\n    /**\n     * Adds a map of environment variables to the current map.\n     *\n     * @param environment the map of environment variables\n     * @return this\n     */\n    public Builder addEnvironment(@Nullable Map<String, String> environment) {\n      if (environment != null) {\n        environmentBuilder.putAll(environment);\n      }\n      return this;\n    }\n\n    /**\n     * Adds an environment variable with a given name and value.\n     *\n     * @param name the name of the variable\n     * @param value the value to set it to\n     * @return this\n     */\n    public Builder addEnvironmentVariable(String name, String value) {\n      environmentBuilder.put(name, value);\n      return this;\n    }\n\n    /**\n     * Sets the entrypoint of the image.\n     *\n     * @param entrypoint the list of entrypoint tokens\n     * @return this\n     */\n    public Builder setEntrypoint(@Nullable List<String> entrypoint) {\n      this.entrypoint = (entrypoint == null) ? null : ImmutableList.copyOf(entrypoint);\n      return this;\n    }\n\n    /**\n     * Sets the user/group to run the container as.\n     *\n     * @param user the username/UID and optionally the groupname/GID\n     * @return this\n     */\n    public Builder setUser(@Nullable String user) {\n      this.user = user;\n      return this;\n    }\n\n    /**\n     * Sets the items in the \"Cmd\" field in the container configuration.\n     *\n     * @param programArguments the list of arguments to append to the image entrypoint\n     * @return this\n     */\n    public Builder setProgramArguments(@Nullable List<String> programArguments) {\n      this.programArguments =\n          (programArguments == null) ? null : ImmutableList.copyOf(programArguments);\n      return this;\n    }\n\n    /**\n     * Sets the container's healthcheck configuration.\n     *\n     * @param healthCheck the healthcheck configuration\n     * @return this\n     */\n    public Builder setHealthCheck(@Nullable DockerHealthCheck healthCheck) {\n      this.healthCheck = healthCheck;\n      return this;\n    }\n\n    /**\n     * Adds items to the \"ExposedPorts\" field in the container configuration.\n     *\n     * @param exposedPorts the exposed ports to add\n     * @return this\n     */\n    public Builder addExposedPorts(@Nullable Set<Port> exposedPorts) {\n      if (exposedPorts != null) {\n        exposedPortsBuilder.addAll(exposedPorts);\n      }\n      return this;\n    }\n\n    /**\n     * Adds items to the \"Volumes\" field in the container configuration.\n     *\n     * @param volumes the directories to create volumes\n     * @return this\n     */\n    public Builder addVolumes(@Nullable Set<AbsoluteUnixPath> volumes) {\n      if (volumes != null) {\n        volumesBuilder.addAll(ImmutableSet.copyOf(volumes));\n      }\n      return this;\n    }\n\n    /**\n     * Adds items to the \"Labels\" field in the container configuration.\n     *\n     * @param labels the map of labels to add\n     * @return this\n     */\n    public Builder addLabels(@Nullable Map<String, String> labels) {\n      if (labels != null) {\n        labelsBuilder.putAll(labels);\n      }\n      return this;\n    }\n\n    /**\n     * Adds an item to the \"Labels\" field in the container configuration.\n     *\n     * @param name the name of the label\n     * @param value the value of the label\n     * @return this\n     */\n    public Builder addLabel(String name, String value) {\n      labelsBuilder.put(name, value);\n      return this;\n    }\n\n    /**\n     * Sets the item in the \"WorkingDir\" field in the container configuration.\n     *\n     * @param workingDirectory the working directory\n     * @return this\n     */\n    public Builder setWorkingDirectory(@Nullable String workingDirectory) {\n      this.workingDirectory = workingDirectory;\n      return this;\n    }\n\n    /**\n     * Adds a layer to the image.\n     *\n     * @param layer the layer to add\n     * @return this\n     * @throws LayerPropertyNotFoundException if adding the layer fails\n     */\n    public Builder addLayer(Layer layer) throws LayerPropertyNotFoundException {\n      imageLayersBuilder.add(layer);\n      return this;\n    }\n\n    /**\n     * Adds a history element to the image.\n     *\n     * @param history the history object to add\n     * @return this\n     */\n    public Builder addHistory(HistoryEntry history) {\n      historyBuilder.add(history);\n      return this;\n    }\n\n    /**\n     * Create an {@link Image} instance.\n     *\n     * @return a new {@link Image} instance\n     */\n    public Image build() {\n      return new Image(\n          imageFormat,\n          created,\n          architecture,\n          os,\n          imageLayersBuilder.build(),\n          historyBuilder.build(),\n          ImmutableMap.copyOf(environmentBuilder),\n          entrypoint,\n          programArguments,\n          healthCheck,\n          ImmutableSet.copyOf(exposedPortsBuilder),\n          ImmutableSet.copyOf(volumesBuilder),\n          ImmutableMap.copyOf(labelsBuilder),\n          workingDirectory,\n          user);\n    }\n  }\n\n  public static Builder builder(Class<? extends ManifestTemplate> imageFormat) {\n    return new Builder(imageFormat);\n  }\n\n  /** The image format. */\n  private final Class<? extends ManifestTemplate> imageFormat;\n\n  /** The image creation time. */\n  @Nullable private final Instant created;\n\n  /** The image architecture. */\n  private final String architecture;\n\n  /** The image operating system. */\n  private final String os;\n\n  /** The layers of the image, in the order in which they are applied. */\n  private final ImmutableList<Layer> layers;\n\n  /** The commands used to build each layer of the image. */\n  private final ImmutableList<HistoryEntry> history;\n\n  /** Environment variable definitions for running the image, in the format {@code NAME=VALUE}. */\n  @Nullable private final ImmutableMap<String, String> environment;\n\n  /** Initial command to run when running the image. */\n  @Nullable private final ImmutableList<String> entrypoint;\n\n  /** Arguments to append to the image entrypoint when running the image. */\n  @Nullable private final ImmutableList<String> programArguments;\n\n  /** Healthcheck configuration. */\n  @Nullable private final DockerHealthCheck healthCheck;\n\n  /** Ports that the container listens on. */\n  @Nullable private final ImmutableSet<Port> exposedPorts;\n\n  /** Directories to mount as volumes. */\n  @Nullable private final ImmutableSet<AbsoluteUnixPath> volumes;\n\n  /** Labels on the container configuration. */\n  @Nullable private final ImmutableMap<String, String> labels;\n\n  /** Working directory on the container configuration. */\n  @Nullable private final String workingDirectory;\n\n  /** User on the container configuration. */\n  @Nullable private final String user;\n\n  private Image(\n      Class<? extends ManifestTemplate> imageFormat,\n      @Nullable Instant created,\n      String architecture,\n      String os,\n      ImmutableList<Layer> layers,\n      ImmutableList<HistoryEntry> history,\n      @Nullable ImmutableMap<String, String> environment,\n      @Nullable ImmutableList<String> entrypoint,\n      @Nullable ImmutableList<String> programArguments,\n      @Nullable DockerHealthCheck healthCheck,\n      @Nullable ImmutableSet<Port> exposedPorts,\n      @Nullable ImmutableSet<AbsoluteUnixPath> volumes,\n      @Nullable ImmutableMap<String, String> labels,\n      @Nullable String workingDirectory,\n      @Nullable String user) {\n    this.imageFormat = imageFormat;\n    this.created = created;\n    this.architecture = architecture;\n    this.os = os;\n    this.layers = layers;\n    this.history = history;\n    this.environment = environment;\n    this.entrypoint = entrypoint;\n    this.programArguments = programArguments;\n    this.healthCheck = healthCheck;\n    this.exposedPorts = exposedPorts;\n    this.volumes = volumes;\n    this.labels = labels;\n    this.workingDirectory = workingDirectory;\n    this.user = user;\n  }\n\n  public Class<? extends ManifestTemplate> getImageFormat() {\n    return imageFormat;\n  }\n\n  @Nullable\n  public Instant getCreated() {\n    return created;\n  }\n\n  public String getArchitecture() {\n    return architecture;\n  }\n\n  public String getOs() {\n    return os;\n  }\n\n  @Nullable\n  public ImmutableMap<String, String> getEnvironment() {\n    return environment;\n  }\n\n  @Nullable\n  public ImmutableList<String> getEntrypoint() {\n    return entrypoint;\n  }\n\n  @Nullable\n  public ImmutableList<String> getProgramArguments() {\n    return programArguments;\n  }\n\n  @Nullable\n  public DockerHealthCheck getHealthCheck() {\n    return healthCheck;\n  }\n\n  @Nullable\n  public ImmutableSet<Port> getExposedPorts() {\n    return exposedPorts;\n  }\n\n  @Nullable\n  public ImmutableSet<AbsoluteUnixPath> getVolumes() {\n    return volumes;\n  }\n\n  @Nullable\n  public ImmutableMap<String, String> getLabels() {\n    return labels;\n  }\n\n  @Nullable\n  public String getWorkingDirectory() {\n    return workingDirectory;\n  }\n\n  @Nullable\n  public String getUser() {\n    return user;\n  }\n\n  public ImmutableList<Layer> getLayers() {\n    return layers;\n  }\n\n  public ImmutableList<HistoryEntry> getHistory() {\n    return history;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/ImageTarball.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.docker.json.DockerManifestEntryTemplate;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.json.ImageToJsonTranslator;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.tar.TarStreamBuilder;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Instant;\nimport java.util.Collections;\n\n/** Translates an {@link Image} to a tarball that can be loaded into Docker. */\npublic class ImageTarball {\n\n  /** File name for the container configuration in the tarball. */\n  private static final String CONTAINER_CONFIGURATION_JSON_FILE_NAME = \"config.json\";\n\n  /** File name for the manifest in the tarball. */\n  private static final String MANIFEST_JSON_FILE_NAME = \"manifest.json\";\n\n  /** File name extension for the layer content files. */\n  private static final String LAYER_FILE_EXTENSION = \".tar.gz\";\n\n  /** Time that entry is set in the tar. */\n  private static final Instant TAR_ENTRY_MODIFICATION_TIME = Instant.EPOCH;\n\n  private static final String BLOB_PREFIX = \"blobs/sha256/\";\n\n  private final Image image;\n  private final ImageReference imageReference;\n  private final ImmutableSet<String> allTargetImageTags;\n\n  /**\n   * Instantiate with an {@link Image}.\n   *\n   * @param image the image to convert into a tarball\n   * @param imageReference image reference to set in the manifest (note that the tag portion of the\n   *     image reference is ignored)\n   * @param allTargetImageTags the tags to tag the image with\n   */\n  public ImageTarball(\n      Image image, ImageReference imageReference, ImmutableSet<String> allTargetImageTags) {\n    this.image = image;\n    this.imageReference = imageReference;\n    this.allTargetImageTags = allTargetImageTags;\n  }\n\n  /**\n   * Writes image tar bar in configured {@link Image#getImageFormat()} of OCI or Docker to output\n   * stream.\n   *\n   * @param out the target output stream\n   * @throws IOException if an error occurs writing out the image to stream\n   */\n  public void writeTo(OutputStream out) throws IOException {\n    if (image.getImageFormat() == OciManifestTemplate.class) {\n      ociWriteTo(out);\n    } else {\n      dockerWriteTo(out);\n    }\n  }\n\n  private void ociWriteTo(OutputStream out) throws IOException {\n    TarStreamBuilder tarStreamBuilder = new TarStreamBuilder();\n    OciManifestTemplate manifest = new OciManifestTemplate();\n\n    // Adds all the layers to the tarball and manifest\n    for (Layer layer : image.getLayers()) {\n      DescriptorDigest digest = layer.getBlobDescriptor().getDigest();\n      long size = layer.getBlobDescriptor().getSize();\n\n      tarStreamBuilder.addBlobEntry(\n          layer.getBlob(), size, BLOB_PREFIX + digest.getHash(), TAR_ENTRY_MODIFICATION_TIME);\n      manifest.addLayer(size, digest);\n    }\n\n    // Adds the container configuration to the tarball and manifest\n    JsonTemplate containerConfiguration =\n        new ImageToJsonTranslator(image).getContainerConfiguration();\n    BlobDescriptor configDescriptor = Digests.computeDigest(containerConfiguration);\n    manifest.setContainerConfiguration(configDescriptor.getSize(), configDescriptor.getDigest());\n    tarStreamBuilder.addByteEntry(\n        JsonTemplateMapper.toByteArray(containerConfiguration),\n        BLOB_PREFIX + configDescriptor.getDigest().getHash(),\n        TAR_ENTRY_MODIFICATION_TIME);\n\n    // Adds the manifest to the tarball\n    BlobDescriptor manifestDescriptor = Digests.computeDigest(manifest);\n    tarStreamBuilder.addByteEntry(\n        JsonTemplateMapper.toByteArray(manifest),\n        BLOB_PREFIX + manifestDescriptor.getDigest().getHash(),\n        TAR_ENTRY_MODIFICATION_TIME);\n\n    // Adds the oci-layout and index.json\n    tarStreamBuilder.addByteEntry(\n        \"{\\\"imageLayoutVersion\\\": \\\"1.0.0\\\"}\".getBytes(StandardCharsets.UTF_8),\n        \"oci-layout\",\n        TAR_ENTRY_MODIFICATION_TIME);\n    OciIndexTemplate index = new OciIndexTemplate();\n    // TODO: figure out how to tag with allTargetImageTags\n    index.addManifest(manifestDescriptor, imageReference.toStringWithQualifier());\n    tarStreamBuilder.addByteEntry(\n        JsonTemplateMapper.toByteArray(index), \"index.json\", TAR_ENTRY_MODIFICATION_TIME);\n\n    tarStreamBuilder.writeAsTarArchiveTo(out);\n  }\n\n  private void dockerWriteTo(OutputStream out) throws IOException {\n    TarStreamBuilder tarStreamBuilder = new TarStreamBuilder();\n    DockerManifestEntryTemplate manifestTemplate = new DockerManifestEntryTemplate();\n\n    // Adds all the layers to the tarball and manifest.\n    for (Layer layer : image.getLayers()) {\n      String layerName = layer.getBlobDescriptor().getDigest().getHash() + LAYER_FILE_EXTENSION;\n\n      tarStreamBuilder.addBlobEntry(\n          layer.getBlob(),\n          layer.getBlobDescriptor().getSize(),\n          layerName,\n          TAR_ENTRY_MODIFICATION_TIME);\n      manifestTemplate.addLayerFile(layerName);\n    }\n\n    // Adds the container configuration to the tarball.\n    JsonTemplate containerConfiguration =\n        new ImageToJsonTranslator(image).getContainerConfiguration();\n    tarStreamBuilder.addByteEntry(\n        JsonTemplateMapper.toByteArray(containerConfiguration),\n        CONTAINER_CONFIGURATION_JSON_FILE_NAME,\n        TAR_ENTRY_MODIFICATION_TIME);\n\n    // Adds the manifest to tarball.\n    for (String tag : allTargetImageTags) {\n      manifestTemplate.addRepoTag(imageReference.withQualifier(tag).toStringWithQualifier());\n    }\n    tarStreamBuilder.addByteEntry(\n        JsonTemplateMapper.toByteArray(Collections.singletonList(manifestTemplate)),\n        MANIFEST_JSON_FILE_NAME,\n        TAR_ENTRY_MODIFICATION_TIME);\n\n    tarStreamBuilder.writeAsTarArchiveTo(out);\n  }\n\n  /**\n   * Returns the total size of the image's layers in bytes.\n   *\n   * @return the total size of the image's layers in bytes\n   */\n  public long getTotalLayerSize() {\n    long size = 0;\n    for (Layer layer : image.getLayers()) {\n      size += layer.getBlobDescriptor().getSize();\n    }\n    return size;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/Layer.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\n\n/**\n * Represents a layer in an image. Implementations represent the various types of layers.\n *\n * <p>An image layer consists of:\n *\n * <ul>\n *   <li>Content BLOB\n *   <li>\n *       <ul>\n *         <li>The compressed archive (tarball gzip) of the partial filesystem changeset.\n *       </ul>\n *   <li>Content Digest\n *   <li>\n *       <ul>\n *         <li>The SHA-256 hash of the content BLOB.\n *       </ul>\n *   <li>Content Size\n *   <li>\n *       <ul>\n *         <li>The size (in bytes) of the content BLOB.\n *       </ul>\n *   <li>Diff ID\n *   <li>\n *       <ul>\n *         <li>The SHA-256 hash of the uncompressed archive (tarball) of the partial filesystem\n *             changeset.\n *       </ul>\n * </ul>\n */\npublic interface Layer {\n\n  /**\n   * Returns this layer's contents.\n   *\n   * @return the layer's content BLOB\n   * @throws LayerPropertyNotFoundException if not available\n   */\n  Blob getBlob() throws LayerPropertyNotFoundException;\n\n  // TODO: Remove this\n  /**\n   * Returns this layer's content descriptor.\n   *\n   * @return the layer's content {@link BlobDescriptor}\n   * @throws LayerPropertyNotFoundException if not available\n   */\n  BlobDescriptor getBlobDescriptor() throws LayerPropertyNotFoundException;\n\n  /**\n   * Returns this layer's diff ID.\n   *\n   * @return the layer's diff ID\n   * @throws LayerPropertyNotFoundException if not available\n   */\n  DescriptorDigest getDiffId() throws LayerPropertyNotFoundException;\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/LayerCountMismatchException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\n/** Exception thrown when the number of layers found did not match expectations. */\npublic class LayerCountMismatchException extends Exception {\n\n  public LayerCountMismatchException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/LayerPropertyNotFoundException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\n/** Exception thrown when accessing non-existent properties of layers. */\npublic class LayerPropertyNotFoundException extends RuntimeException {\n\n  LayerPropertyNotFoundException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/ReferenceLayer.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\n\n/**\n * A {@link Layer} reference that <b>does not</b> have the underlying content. It references the\n * layer with its digest, size, and diff ID.\n */\npublic class ReferenceLayer implements Layer {\n\n  /** The {@link BlobDescriptor} of the compressed layer content. */\n  private final BlobDescriptor blobDescriptor;\n\n  /** The digest of the uncompressed layer content. */\n  private final DescriptorDigest diffId;\n\n  /**\n   * Instantiate with a {@link BlobDescriptor} and diff ID.\n   *\n   * @param blobDescriptor the blob descriptor\n   * @param diffId the diff ID\n   */\n  public ReferenceLayer(BlobDescriptor blobDescriptor, DescriptorDigest diffId) {\n    this.blobDescriptor = blobDescriptor;\n    this.diffId = diffId;\n  }\n\n  @Override\n  public Blob getBlob() throws LayerPropertyNotFoundException {\n    throw new LayerPropertyNotFoundException(\"Blob not available for reference layer\");\n  }\n\n  @Override\n  public BlobDescriptor getBlobDescriptor() {\n    return blobDescriptor;\n  }\n\n  @Override\n  public DescriptorDigest getDiffId() {\n    return diffId;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/ReferenceNoDiffIdLayer.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\n\n/**\n * A {@link Layer} reference that <b>does not</b> have the underlying content. It references the\n * layer with its digest and size, but <b>not</b> its diff ID.\n */\npublic class ReferenceNoDiffIdLayer implements Layer {\n\n  /** The {@link BlobDescriptor} of the compressed layer content. */\n  private final BlobDescriptor blobDescriptor;\n\n  /**\n   * Instantiate with a {@link BlobDescriptor} and no diff ID.\n   *\n   * @param blobDescriptor the blob descriptor\n   */\n  public ReferenceNoDiffIdLayer(BlobDescriptor blobDescriptor) {\n    this.blobDescriptor = blobDescriptor;\n  }\n\n  @Override\n  public Blob getBlob() throws LayerPropertyNotFoundException {\n    throw new LayerPropertyNotFoundException(\n        \"Blob not available for reference layer without diff ID\");\n  }\n\n  @Override\n  public BlobDescriptor getBlobDescriptor() {\n    return blobDescriptor;\n  }\n\n  @Override\n  public DescriptorDigest getDiffId() throws LayerPropertyNotFoundException {\n    throw new LayerPropertyNotFoundException(\n        \"Diff ID not available for reference layer without diff ID\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.tar.TarStreamBuilder;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\n\n/**\n * Builds a reproducible layer {@link Blob} from files. The reproducibility is implemented by strips\n * out all non-reproducible elements (modification time, group ID, user ID, user name, and group\n * name) from name-sorted tar archive entries.\n */\npublic class ReproducibleLayerBuilder {\n\n  /**\n   * Holds a list of {@link TarArchiveEntry}s with unique extraction paths. The list also includes\n   * all parent directories for each extraction path.\n   */\n  private static class UniqueTarArchiveEntries {\n\n    /**\n     * Uses the current directory to act as the file input to TarArchiveEntry (since all directories\n     * are treated the same in {@link TarArchiveEntry#TarArchiveEntry(File, String)}, except for\n     * modification time, UID, GID, etc., which are wiped away in {@link #build}).\n     */\n    private static final Path DIRECTORY_FILE = Paths.get(\".\");\n\n    private final List<TarArchiveEntry> entries = new ArrayList<>();\n    private final Set<String> names = new HashSet<>();\n\n    /**\n     * Adds a {@link TarArchiveEntry} if its extraction path does not exist yet. Also adds all of\n     * the parent directories on the extraction path, if the parent does not exist. Parent will have\n     * modification time set to {@link FileEntriesLayer#DEFAULT_MODIFICATION_TIME}.\n     *\n     * @param tarArchiveEntry the {@link TarArchiveEntry}\n     * @throws IOException if an I/O error occurs\n     */\n    private void add(TarArchiveEntry tarArchiveEntry) throws IOException {\n      if (names.contains(tarArchiveEntry.getName())) {\n        return;\n      }\n\n      // Adds all directories along extraction paths to explicitly set permissions for those\n      // directories.\n      Path namePath = Paths.get(tarArchiveEntry.getName());\n      if (namePath.getParent() != namePath.getRoot()) {\n        Path tarArchiveParentDir = Verify.verifyNotNull(namePath.getParent());\n        TarArchiveEntry dir = new TarArchiveEntry(DIRECTORY_FILE, tarArchiveParentDir.toString());\n        dir.setUserId(0);\n        dir.setGroupId(0);\n        dir.setUserName(\"\");\n        dir.setGroupName(\"\");\n        clearTimeHeaders(dir, FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n        add(dir);\n      }\n\n      entries.add(tarArchiveEntry);\n      names.add(tarArchiveEntry.getName());\n    }\n\n    private List<TarArchiveEntry> getSortedEntries() {\n      List<TarArchiveEntry> sortedEntries = new ArrayList<>(entries);\n      sortedEntries.sort(Comparator.comparing(TarArchiveEntry::getName));\n      return sortedEntries;\n    }\n  }\n\n  private static void clearTimeHeaders(TarArchiveEntry entry, Instant modTime) {\n    entry.setModTime(modTime.toEpochMilli());\n\n    String headerTime = Long.toString(modTime.getEpochSecond());\n    final long nanos = modTime.getNano();\n    if (nanos > 0) {\n      headerTime += \".\" + nanos;\n    }\n    entry.addPaxHeader(\"mtime\", headerTime);\n    entry.addPaxHeader(\"atime\", headerTime);\n    entry.addPaxHeader(\"ctime\", headerTime);\n    entry.addPaxHeader(\"LIBARCHIVE.creationtime\", headerTime);\n  }\n\n  private static void setUserAndGroup(TarArchiveEntry entry, FileEntry layerEntry) {\n    entry.setUserId(0);\n    entry.setGroupId(0);\n    entry.setUserName(\"\");\n    entry.setGroupName(\"\");\n\n    if (!layerEntry.getOwnership().isEmpty()) {\n      // Parse \"<user>:<group>\" string.\n      String user = layerEntry.getOwnership();\n      String group = \"\";\n      int colonIndex = user.indexOf(':');\n      if (colonIndex != -1) {\n        group = user.substring(colonIndex + 1);\n        user = user.substring(0, colonIndex);\n      }\n\n      if (!user.isEmpty()) {\n        // Check if it's a number, and set either UID or user name.\n        try {\n          entry.setUserId(Long.parseLong(user));\n        } catch (NumberFormatException ignored) {\n          entry.setUserName(user);\n        }\n      }\n      if (!group.isEmpty()) {\n        // Check if it's a number, and set either GID or group name.\n        try {\n          entry.setGroupId(Long.parseLong(group));\n        } catch (NumberFormatException ignored) {\n          entry.setGroupName(group);\n        }\n      }\n    }\n  }\n\n  private final ImmutableList<FileEntry> layerEntries;\n\n  public ReproducibleLayerBuilder(ImmutableList<FileEntry> layerEntries) {\n    this.layerEntries = layerEntries;\n  }\n\n  /**\n   * Builds and returns the layer {@link Blob}.\n   *\n   * @return the new layer\n   * @throws IOException if an I/O error occurs\n   */\n  public Blob build() throws IOException {\n    UniqueTarArchiveEntries uniqueTarArchiveEntries = new UniqueTarArchiveEntries();\n\n    // Adds all the layer entries as tar entries.\n    for (FileEntry layerEntry : layerEntries) {\n      // Adds the entries to uniqueTarArchiveEntries, which makes sure all entries are unique and\n      // adds parent directories for each extraction path.\n      TarArchiveEntry entry =\n          new TarArchiveEntry(\n              layerEntry.getSourceFile(), layerEntry.getExtractionPath().toString());\n\n      // Sets the entry's permissions by masking out the permission bits from the entry's mode (the\n      // lowest 9 bits) then using a bitwise OR to set them to the layerEntry's permissions.\n      entry.setMode((entry.getMode() & ~0777) | layerEntry.getPermissions().getPermissionBits());\n      setUserAndGroup(entry, layerEntry);\n      clearTimeHeaders(entry, layerEntry.getModificationTime());\n\n      uniqueTarArchiveEntries.add(entry);\n    }\n\n    // Gets the entries sorted by extraction path.\n    List<TarArchiveEntry> sortedFilesystemEntries = uniqueTarArchiveEntries.getSortedEntries();\n\n    Set<String> names = new HashSet<>();\n\n    // Adds all the files to a tar stream.\n    TarStreamBuilder tarStreamBuilder = new TarStreamBuilder();\n    for (TarArchiveEntry entry : sortedFilesystemEntries) {\n      Verify.verify(!names.contains(entry.getName()));\n      names.add(entry.getName());\n\n      tarStreamBuilder.addTarArchiveEntry(entry);\n    }\n\n    return Blobs.from(tarStreamBuilder::writeAsTarArchiveTo, false);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/BadContainerConfigurationFormatException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\n/** Exception thrown when trying to parse a bad image configuration format. */\npublic class BadContainerConfigurationFormatException extends Exception {\n\n  // TODO: Potentially provide Path or source object to problem configuration file\n  BadContainerConfigurationFormatException(String message) {\n    super(message);\n  }\n\n  BadContainerConfigurationFormatException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/BuildableManifestTemplate.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\n/**\n * Parent class for image manifest JSON templates that can be built.\n *\n * @see V22ManifestTemplate Docker V2.2 format\n * @see OciManifestTemplate OCI format\n */\npublic interface BuildableManifestTemplate extends ManifestTemplate {\n\n  /**\n   * Template for inner JSON object representing content descriptor for a layer or container\n   * configuration.\n   *\n   * @see <a href=\"https://github.com/opencontainers/image-spec/blob/master/descriptor.md\">OCI\n   *     Content Descriptors</a>\n   */\n  class ContentDescriptorTemplate implements JsonTemplate {\n\n    @SuppressWarnings(\"unused\")\n    @Nullable\n    private String mediaType;\n\n    @Nullable private DescriptorDigest digest;\n    private long size;\n    @Nullable private List<String> urls;\n    @Nullable private Map<String, String> annotations;\n\n    ContentDescriptorTemplate(String mediaType, long size, DescriptorDigest digest) {\n      this.mediaType = mediaType;\n      this.size = size;\n      this.digest = digest;\n    }\n\n    /** Necessary for Jackson to create from JSON. */\n    @SuppressWarnings(\"unused\")\n    protected ContentDescriptorTemplate() {}\n\n    public long getSize() {\n      return size;\n    }\n\n    void setSize(long size) {\n      this.size = size;\n    }\n\n    @Nullable\n    public DescriptorDigest getDigest() {\n      return digest;\n    }\n\n    void setDigest(DescriptorDigest digest) {\n      this.digest = digest;\n    }\n\n    @VisibleForTesting\n    @Nullable\n    public List<String> getUrls() {\n      return urls;\n    }\n\n    void setUrls(List<String> urls) {\n      this.urls = ImmutableList.copyOf(urls);\n    }\n\n    @VisibleForTesting\n    @Nullable\n    public Map<String, String> getAnnotations() {\n      return annotations;\n    }\n\n    void setAnnotations(Map<String, String> annotations) {\n      this.annotations = ImmutableMap.copyOf(annotations);\n    }\n  }\n\n  /**\n   * Returns the media type for this manifest, specific to the image format.\n   *\n   * @return the media type for this manifest, specific to the image format\n   */\n  @Override\n  String getManifestMediaType();\n\n  /**\n   * Returns the content descriptor of the container configuration.\n   *\n   * @return the content descriptor of the container configuration\n   */\n  @Nullable\n  ContentDescriptorTemplate getContainerConfiguration();\n\n  /**\n   * Returns an unmodifiable view of the layers.\n   *\n   * @return an unmodifiable view of the layers\n   */\n  List<ContentDescriptorTemplate> getLayers();\n\n  /**\n   * Sets the content descriptor of the container configuration.\n   *\n   * @param size the size of the container configuration.\n   * @param digest the container configuration content descriptor digest.\n   */\n  void setContainerConfiguration(long size, DescriptorDigest digest);\n\n  /**\n   * Adds a layer to the manifest.\n   *\n   * @param size the size of the layer.\n   * @param digest the layer descriptor digest.\n   */\n  void addLayer(long size, DescriptorDigest digest);\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ContainerConfigurationTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.base.Preconditions;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\n/**\n * JSON Template for Docker Container Configuration referenced in Docker Manifest Schema V2.2\n *\n * <p>Example container config JSON:\n *\n * <pre>{@code\n * {\n *   \"created\": \"1970-01-01T00:00:00Z\",\n *   \"architecture\": \"amd64\",\n *   \"os\": \"linux\",\n *   \"config\": {\n *     \"Env\": [\"/usr/bin/java\"],\n *     \"Entrypoint\": [\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\n *     \"Cmd\": [\"arg1\", \"arg2\"],\n *     \"Healthcheck\": {\n *       \"Test\": [\"CMD-SHELL\", \"/usr/bin/check-health localhost\"],\n *       \"Interval\": 30000000000,\n *       \"Timeout\": 10000000000,\n *       \"StartPeriod\": 0,\n *       \"Retries\": 3\n *     }\n *     \"ExposedPorts\": { \"6000/tcp\":{}, \"8000/tcp\":{}, \"9000/tcp\":{} },\n *     \"Volumes\":{\"/var/job-result-data\":{},\"/var/log/my-app-logs\":{}}},\n *     \"Labels\": { \"com.example.label\": \"value\" },\n *     \"WorkingDir\": \"/home/user/workspace\",\n *     \"User\": \"me\"\n *   },\n *   \"history\": [\n *     {\n *       \"author\": \"Jib\",\n *       \"created\": \"1970-01-01T00:00:00Z\",\n *       \"created_by\": \"jib\"\n *     },\n *     {\n *       \"author\": \"Jib\",\n *       \"created\": \"1970-01-01T00:00:00Z\",\n *       \"created_by\": \"jib\"\n *     }\n *   ]\n *   \"rootfs\": {\n *     \"diff_ids\": [\n *       \"sha256:2aebd096e0e237b447781353379722157e6c2d434b9ec5a0d63f2a6f07cf90c2\",\n *       \"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\",\n *     ],\n *     \"type\": \"layers\"\n *   }\n * }\n * }</pre>\n *\n * @see <a href=\"https://docs.docker.com/registry/spec/manifest-v2-2/\">Image Manifest Version 2,\n *     Schema 2</a>\n */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class ContainerConfigurationTemplate implements JsonTemplate {\n\n  /** ISO-8601 formatted combined date and time at which the image was created. */\n  @Nullable private String created;\n\n  /** The CPU architecture to run the binaries in this container. */\n  private String architecture = \"amd64\";\n\n  /** The operating system to run the container on. */\n  private String os = \"linux\";\n\n  /** Execution parameters that should be used as a base when running the container. */\n  private final ConfigurationObjectTemplate config = new ConfigurationObjectTemplate();\n\n  /** Describes the history of each layer. */\n  private final List<HistoryEntry> history = new ArrayList<>();\n\n  /** Layer content digests that are used to build the container filesystem. */\n  private final RootFilesystemObjectTemplate rootfs = new RootFilesystemObjectTemplate();\n\n  /** Template for inner JSON object representing the configuration for running the container. */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  private static class ConfigurationObjectTemplate implements JsonTemplate {\n\n    /** Environment variables in the format {@code VARNAME=VARVALUE}. */\n    @Nullable private List<String> Env;\n\n    /** Command to run when container starts. */\n    @Nullable private List<String> Entrypoint;\n\n    /** Arguments to pass into main. */\n    @Nullable private List<String> Cmd;\n\n    /** Healthcheck. */\n    @Nullable private HealthCheckObjectTemplate Healthcheck;\n\n    /** Network ports the container exposes. */\n    @Nullable private Map<String, Map<String, String>> ExposedPorts;\n\n    /** Labels. */\n    @Nullable private Map<String, String> Labels;\n\n    /** Working directory. */\n    @Nullable private String WorkingDir;\n\n    /** User. */\n    @Nullable private String User;\n\n    /** Volumes. */\n    @Nullable private Map<String, Map<String, String>> Volumes;\n  }\n\n  /** Template for inner JSON object representing the healthcheck configuration. */\n  private static class HealthCheckObjectTemplate implements JsonTemplate {\n\n    /** The test to perform to check that the container is healthy. */\n    @Nullable private List<String> Test;\n\n    /** Number of nanoseconds to wait between probe attempts. */\n    @Nullable private Long Interval;\n\n    /** Number of nanoseconds to wait before considering the check to have hung. */\n    @Nullable private Long Timeout;\n\n    /**\n     * Number of nanoseconds to wait for the container to initialize before starting health-retries.\n     */\n    @Nullable private Long StartPeriod;\n\n    /** The number of consecutive failures needed to consider the container as unhealthy. */\n    @Nullable private Integer Retries;\n  }\n\n  /**\n   * Template for inner JSON object representing the filesystem changesets used to build the\n   * container filesystem.\n   */\n  private static class RootFilesystemObjectTemplate implements JsonTemplate {\n\n    /** The type must always be {@code \"layers\"}. */\n    @SuppressWarnings(\"unused\")\n    private final String type = \"layers\";\n\n    /**\n     * The in-order list of layer content digests (hashes of the uncompressed partial filesystem\n     * changeset).\n     */\n    private final List<DescriptorDigest> diff_ids = new ArrayList<>();\n  }\n\n  public void setCreated(@Nullable String created) {\n    this.created = created;\n  }\n\n  /**\n   * Sets the architecture for which this container was built. See the <a\n   * href=\"https://github.com/opencontainers/image-spec/blob/master/config.md#properties\">OCI Image\n   * Configuration specification</a> for acceptable values.\n   *\n   * @param architecture value for the {@code architecture} field\n   */\n  public void setArchitecture(String architecture) {\n    this.architecture = architecture;\n  }\n\n  /**\n   * Sets the operating system for which this container was built. See the <a\n   * href=\"https://github.com/opencontainers/image-spec/blob/master/config.md#properties\">OCI Image\n   * Configuration specification</a> for acceptable values.\n   *\n   * @param os value for the {@code os} field\n   */\n  public void setOs(String os) {\n    this.os = os;\n  }\n\n  public void setContainerEnvironment(@Nullable List<String> environment) {\n    config.Env = environment;\n  }\n\n  public void setContainerEntrypoint(@Nullable List<String> command) {\n    config.Entrypoint = command;\n  }\n\n  public void setContainerCmd(@Nullable List<String> cmd) {\n    config.Cmd = cmd;\n  }\n\n  /**\n   * Sets test on HealthCheck, creates an empty HealthCheck object if necessary.\n   *\n   * @param test the list of tests to set\n   */\n  public void setContainerHealthCheckTest(List<String> test) {\n    if (config.Healthcheck == null) {\n      config.Healthcheck = new HealthCheckObjectTemplate();\n    }\n    Preconditions.checkNotNull(config.Healthcheck).Test = test;\n  }\n\n  /**\n   * Sets interval on HealthCheck, creates an empty HealthCheck object if necessary.\n   *\n   * @param interval the interval to set\n   */\n  public void setContainerHealthCheckInterval(@Nullable Long interval) {\n    if (config.Healthcheck == null) {\n      config.Healthcheck = new HealthCheckObjectTemplate();\n    }\n    Preconditions.checkNotNull(config.Healthcheck).Interval = interval;\n  }\n\n  /**\n   * Sets timeout on HealthCheck, creates an empty HealthCheck object if necessary.\n   *\n   * @param timeout the timeout to configure\n   */\n  public void setContainerHealthCheckTimeout(@Nullable Long timeout) {\n    if (config.Healthcheck == null) {\n      config.Healthcheck = new HealthCheckObjectTemplate();\n    }\n    Preconditions.checkNotNull(config.Healthcheck).Timeout = timeout;\n  }\n\n  /**\n   * Sets startPeriod on HealthCheck, creates an empty HealthCheck object if necessary.\n   *\n   * @param startPeriod the start period to configure\n   */\n  public void setContainerHealthCheckStartPeriod(@Nullable Long startPeriod) {\n    if (config.Healthcheck == null) {\n      config.Healthcheck = new HealthCheckObjectTemplate();\n    }\n    Preconditions.checkNotNull(config.Healthcheck).StartPeriod = startPeriod;\n  }\n\n  /**\n   * Sets retries on HealthCheck, creates an empty HealthCheck object if necessary.\n   *\n   * @param retries the number of retries to configure\n   */\n  public void setContainerHealthCheckRetries(@Nullable Integer retries) {\n    if (config.Healthcheck == null) {\n      config.Healthcheck = new HealthCheckObjectTemplate();\n    }\n    Preconditions.checkNotNull(config.Healthcheck).Retries = retries;\n  }\n\n  public void setContainerExposedPorts(@Nullable Map<String, Map<String, String>> exposedPorts) {\n    config.ExposedPorts = exposedPorts;\n  }\n\n  public void setContainerLabels(@Nullable Map<String, String> labels) {\n    config.Labels = labels;\n  }\n\n  public void setContainerWorkingDir(@Nullable String workingDirectory) {\n    config.WorkingDir = workingDirectory;\n  }\n\n  public void setContainerUser(@Nullable String user) {\n    config.User = user;\n  }\n\n  public void setContainerVolumes(@Nullable Map<String, Map<String, String>> volumes) {\n    config.Volumes = volumes;\n  }\n\n  public void addLayerDiffId(DescriptorDigest diffId) {\n    rootfs.diff_ids.add(diffId);\n  }\n\n  public void addHistoryEntry(HistoryEntry historyEntry) {\n    history.add(historyEntry);\n  }\n\n  List<DescriptorDigest> getDiffIds() {\n    return rootfs.diff_ids;\n  }\n\n  List<HistoryEntry> getHistory() {\n    return history;\n  }\n\n  @Nullable\n  String getCreated() {\n    return created;\n  }\n\n  /**\n   * Returns the architecture for which this container was built. See the <a\n   * href=\"https://github.com/opencontainers/image-spec/blob/master/config.md#properties\">OCI Image\n   * Configuration specification</a> for acceptable values.\n   *\n   * @return the {@code architecture} field\n   */\n  public String getArchitecture() {\n    return architecture;\n  }\n\n  /**\n   * Returns the operating system for which this container was built. See the <a\n   * href=\"https://github.com/opencontainers/image-spec/blob/master/config.md#properties\">OCI Image\n   * Configuration specification</a> for acceptable values.\n   *\n   * @return the {@code os} field\n   */\n  public String getOs() {\n    return os;\n  }\n\n  @Nullable\n  List<String> getContainerEnvironment() {\n    return config.Env;\n  }\n\n  @Nullable\n  List<String> getContainerEntrypoint() {\n    return config.Entrypoint;\n  }\n\n  @Nullable\n  List<String> getContainerCmd() {\n    return config.Cmd;\n  }\n\n  @Nullable\n  List<String> getContainerHealthTest() {\n    return config.Healthcheck == null ? null : config.Healthcheck.Test;\n  }\n\n  @Nullable\n  Long getContainerHealthInterval() {\n    return config.Healthcheck == null ? null : config.Healthcheck.Interval;\n  }\n\n  @Nullable\n  Long getContainerHealthTimeout() {\n    return config.Healthcheck == null ? null : config.Healthcheck.Timeout;\n  }\n\n  @Nullable\n  Long getContainerHealthStartPeriod() {\n    return config.Healthcheck == null ? null : config.Healthcheck.StartPeriod;\n  }\n\n  @Nullable\n  Integer getContainerHealthRetries() {\n    return config.Healthcheck == null ? null : config.Healthcheck.Retries;\n  }\n\n  @Nullable\n  Map<String, Map<String, String>> getContainerExposedPorts() {\n    return config.ExposedPorts;\n  }\n\n  @Nullable\n  Map<String, String> getContainerLabels() {\n    return config.Labels;\n  }\n\n  @Nullable\n  String getContainerWorkingDir() {\n    return config.WorkingDir;\n  }\n\n  @Nullable\n  String getContainerUser() {\n    return config.User;\n  }\n\n  @Nullable\n  Map<String, Map<String, String>> getContainerVolumes() {\n    return config.Volumes;\n  }\n\n  public DescriptorDigest getLayerDiffId(int index) {\n    return rootfs.diff_ids.get(index);\n  }\n\n  public int getLayerCount() {\n    return rootfs.diff_ids.size();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/DescriptorDigestDeserializer.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport java.io.IOException;\nimport java.security.DigestException;\n\n/** Deserializes a JSON element into a {@link DescriptorDigest} object. */\npublic class DescriptorDigestDeserializer extends JsonDeserializer<DescriptorDigest> {\n\n  @Override\n  public DescriptorDigest deserialize(JsonParser jsonParser, DeserializationContext ignored)\n      throws IOException {\n    try {\n      return DescriptorDigest.fromDigest(jsonParser.getValueAsString());\n    } catch (DigestException ex) {\n      throw new IOException(ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/DescriptorDigestSerializer.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport java.io.IOException;\n\n/** Serializes a {@link DescriptorDigest} into JSON element. */\npublic class DescriptorDigestSerializer extends JsonSerializer<DescriptorDigest> {\n\n  @Override\n  public void serialize(\n      DescriptorDigest value, JsonGenerator jsonGenerator, SerializerProvider ignored)\n      throws IOException {\n    jsonGenerator.writeString(value.toString());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/HistoryEntry.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport java.time.Instant;\nimport java.util.Objects;\nimport javax.annotation.Nullable;\n\n/**\n * Represents an item in the container configuration's {@code history} list.\n *\n * @see <a href=https://github.com/opencontainers/image-spec/blob/master/config.md#properties>OCI\n *     image spec ({@code history} field)</a>\n */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class HistoryEntry implements JsonTemplate {\n\n  public static class Builder {\n\n    @Nullable private Instant creationTimestamp;\n    @Nullable private String author;\n    @Nullable private String createdBy;\n    @Nullable private String comment;\n    @Nullable private Boolean emptyLayer;\n\n    public Builder setCreationTimestamp(Instant creationTimestamp) {\n      this.creationTimestamp = creationTimestamp;\n      return this;\n    }\n\n    public Builder setAuthor(String author) {\n      this.author = author;\n      return this;\n    }\n\n    public Builder setCreatedBy(String createdBy) {\n      this.createdBy = createdBy;\n      return this;\n    }\n\n    public Builder setComment(String comment) {\n      this.comment = comment;\n      return this;\n    }\n\n    public Builder setEmptyLayer(Boolean emptyLayer) {\n      this.emptyLayer = emptyLayer;\n      return this;\n    }\n\n    /**\n     * Create a new history entry.\n     *\n     * @return an new {@link HistoryEntry} instance\n     */\n    public HistoryEntry build() {\n      return new HistoryEntry(\n          creationTimestamp == null ? null : creationTimestamp.toString(),\n          author,\n          createdBy,\n          comment,\n          emptyLayer);\n    }\n\n    private Builder() {}\n  }\n\n  /**\n   * Creates a builder for a {@link HistoryEntry}.\n   *\n   * @return the builder\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /** The ISO-8601 formatted timestamp at which the image was created. */\n  @JsonProperty(\"created\")\n  @Nullable\n  private String creationTimestamp;\n\n  /** The name of the author specified when committing the image. */\n  @JsonProperty(\"author\")\n  @Nullable\n  private String author;\n\n  /** The command used to build the layer. */\n  @JsonProperty(\"created_by\")\n  @Nullable\n  private String createdBy;\n\n  /** A custom message set when creating the layer. */\n  @JsonProperty(\"comment\")\n  @Nullable\n  private String comment;\n\n  /**\n   * Whether or not the entry corresponds to a layer in the container ({@code @Nullable Boolean} to\n   * make field optional).\n   */\n  @JsonProperty(\"empty_layer\")\n  @Nullable\n  private Boolean emptyLayer;\n\n  public HistoryEntry() {}\n\n  private HistoryEntry(\n      @Nullable String creationTimestamp,\n      @Nullable String author,\n      @Nullable String createdBy,\n      @Nullable String comment,\n      @Nullable Boolean emptyLayer) {\n    this.author = author;\n    this.creationTimestamp = creationTimestamp;\n    this.createdBy = createdBy;\n    this.comment = comment;\n    this.emptyLayer = emptyLayer;\n  }\n\n  /**\n   * Returns whether or not the history object corresponds to a layer in the container.\n   *\n   * @return {@code true} if the history object corresponds to a layer in the container\n   */\n  @JsonIgnore\n  public boolean hasCorrespondingLayer() {\n    return emptyLayer == null ? false : emptyLayer;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (other instanceof HistoryEntry) {\n      HistoryEntry otherHistory = (HistoryEntry) other;\n      return Objects.equals(otherHistory.creationTimestamp, creationTimestamp)\n          && Objects.equals(otherHistory.author, author)\n          && Objects.equals(otherHistory.createdBy, createdBy)\n          && Objects.equals(otherHistory.comment, comment)\n          && Objects.equals(otherHistory.emptyLayer, emptyLayer);\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(author, creationTimestamp, createdBy, comment, emptyLayer);\n  }\n\n  @Override\n  public String toString() {\n    return createdBy == null ? \"\" : createdBy;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ImageMetadataTemplate.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonSubTypes;\nimport com.fasterxml.jackson.annotation.JsonTypeInfo;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** A bundle of an image manifest list, manifests, and container configurations. */\npublic class ImageMetadataTemplate implements JsonTemplate {\n\n  @JsonTypeInfo(\n      use = JsonTypeInfo.Id.CLASS,\n      include = JsonTypeInfo.As.PROPERTY,\n      property = \"@class\")\n  @JsonSubTypes({\n    @JsonSubTypes.Type(value = OciIndexTemplate.class),\n    @JsonSubTypes.Type(value = V22ManifestListTemplate.class),\n  })\n  @Nullable\n  private ManifestTemplate manifestList;\n\n  private List<ManifestAndConfigTemplate> manifestsAndConfigs = new ArrayList<>();\n\n  @SuppressWarnings(\"unused\")\n  private ImageMetadataTemplate() {}\n\n  public ImageMetadataTemplate(\n      @Nullable ManifestTemplate manifestList,\n      List<ManifestAndConfigTemplate> manifestsAndConfigs) {\n    this.manifestList = manifestList;\n    this.manifestsAndConfigs = manifestsAndConfigs;\n  }\n\n  @Nullable\n  public ManifestTemplate getManifestList() {\n    return manifestList;\n  }\n\n  public List<ManifestAndConfigTemplate> getManifestsAndConfigs() {\n    return manifestsAndConfigs;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ImageToJsonTranslator.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.configuration.DockerHealthCheck;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSortedMap;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\n\n/** Translates an {@link Image} into a manifest or container configuration JSON. */\npublic class ImageToJsonTranslator {\n\n  /**\n   * Converts a set of {@link Port}s to the corresponding container config format for exposed ports\n   * (e.g. {@code Port(1000, Protocol.TCP)} -> {@code {\"1000/tcp\":{}}}).\n   *\n   * @param exposedPorts the set of {@link Port}s to translate, or {@code null}\n   * @return a sorted map with the string representation of the ports as keys and empty maps as\n   *     values, or {@code null} if {@code exposedPorts} is {@code null}\n   */\n  @VisibleForTesting\n  @Nullable\n  static Map<String, Map<String, String>> portSetToMap(@Nullable Set<Port> exposedPorts) {\n    return setToMap(exposedPorts, port -> port.getPort() + \"/\" + port.getProtocol());\n  }\n\n  /**\n   * Converts a set of {@link AbsoluteUnixPath}s to the corresponding container config format for\n   * volumes (e.g. {@code AbsoluteUnixPath().get(\"/var/log/my-app-logs\")} -> {@code\n   * {\"/var/log/my-app-logs\":{}}}).\n   *\n   * @param volumes the set of {@link AbsoluteUnixPath}s to translate, or {@code null}\n   * @return a sorted map with the string representation of the ports as keys and empty maps as\n   *     values, or {@code null} if {@code exposedPorts} is {@code null}\n   */\n  @VisibleForTesting\n  @Nullable\n  static Map<String, Map<String, String>> volumesSetToMap(@Nullable Set<AbsoluteUnixPath> volumes) {\n    return setToMap(volumes, AbsoluteUnixPath::toString);\n  }\n\n  /**\n   * Converts the map of environment variables to a list with items in the format \"NAME=VALUE\".\n   *\n   * @return the list\n   */\n  @VisibleForTesting\n  @Nullable\n  static ImmutableList<String> environmentMapToList(@Nullable Map<String, String> environment) {\n    if (environment == null) {\n      return null;\n    }\n    Preconditions.checkArgument(\n        environment.keySet().stream().noneMatch(key -> key.contains(\"=\")),\n        \"Illegal environment variable: name cannot contain '='\");\n    return environment.entrySet().stream()\n        .map(entry -> entry.getKey() + \"=\" + entry.getValue())\n        .collect(ImmutableList.toImmutableList());\n  }\n\n  /**\n   * Turns a set into a sorted map where each element of the set is mapped to an entry composed by\n   * the key generated with {@code Function<E, String> elementMapper} and an empty map as value.\n   *\n   * <p>This method is needed because the volume object is a direct JSON serialization of the Go\n   * type map[string]struct{} and is represented in JSON as an object mapping its keys to an empty\n   * object.\n   *\n   * <p>Further read at the <a\n   * href=\"https://github.com/opencontainers/image-spec/blob/master/config.md\">image specs.</a>\n   *\n   * @param set the set of elements to be transformed\n   * @param keyMapper the mapper function to generate keys to the map\n   * @param <E> the type of the elements from the set\n   * @return an map\n   */\n  @Nullable\n  private static <E> Map<String, Map<String, String>> setToMap(\n      @Nullable Set<E> set, Function<E, String> keyMapper) {\n    if (set == null) {\n      return null;\n    }\n\n    return set.stream()\n        .collect(\n            ImmutableSortedMap.toImmutableSortedMap(\n                String::compareTo, keyMapper, ignored -> Collections.emptyMap()));\n  }\n\n  private final Image image;\n\n  /**\n   * Instantiate with an {@link Image}.\n   *\n   * @param image the image to translate\n   */\n  public ImageToJsonTranslator(Image image) {\n    this.image = image;\n  }\n\n  /**\n   * Gets the container configuration.\n   *\n   * @return the container configuration\n   */\n  public JsonTemplate getContainerConfiguration() {\n    // Set up the JSON template.\n    ContainerConfigurationTemplate template = new ContainerConfigurationTemplate();\n\n    // Adds the layer diff IDs.\n    for (Layer layer : image.getLayers()) {\n      template.addLayerDiffId(layer.getDiffId());\n    }\n\n    // Adds the history.\n    for (HistoryEntry historyObject : image.getHistory()) {\n      template.addHistoryEntry(historyObject);\n    }\n\n    template.setCreated(image.getCreated() == null ? null : image.getCreated().toString());\n    template.setArchitecture(image.getArchitecture());\n    template.setOs(image.getOs());\n    template.setContainerEnvironment(environmentMapToList(image.getEnvironment()));\n    template.setContainerEntrypoint(image.getEntrypoint());\n    template.setContainerCmd(image.getProgramArguments());\n    template.setContainerExposedPorts(portSetToMap(image.getExposedPorts()));\n    template.setContainerVolumes(volumesSetToMap(image.getVolumes()));\n    template.setContainerLabels(image.getLabels());\n    template.setContainerWorkingDir(image.getWorkingDirectory());\n    template.setContainerUser(image.getUser());\n\n    // Ignore healthcheck if not Docker/command is empty\n    DockerHealthCheck healthCheck = image.getHealthCheck();\n    if (image.getImageFormat() == V22ManifestTemplate.class && healthCheck != null) {\n      template.setContainerHealthCheckTest(healthCheck.getCommand());\n      healthCheck\n          .getInterval()\n          .ifPresent(interval -> template.setContainerHealthCheckInterval(interval.toNanos()));\n      healthCheck\n          .getTimeout()\n          .ifPresent(timeout -> template.setContainerHealthCheckTimeout(timeout.toNanos()));\n      healthCheck\n          .getStartPeriod()\n          .ifPresent(\n              startPeriod -> template.setContainerHealthCheckStartPeriod(startPeriod.toNanos()));\n      template.setContainerHealthCheckRetries(healthCheck.getRetries().orElse(null));\n    }\n\n    return template;\n  }\n\n  /**\n   * Gets the manifest as a JSON template. The {@code containerConfigurationBlobDescriptor} must be\n   * the {@link BlobDescriptor} obtained by writing out the container configuration JSON returned\n   * from {@link #getContainerConfiguration()}.\n   *\n   * @param <T> child type of {@link BuildableManifestTemplate}.\n   * @param manifestTemplateClass the JSON template to translate the image to.\n   * @param containerConfigurationBlobDescriptor the container configuration descriptor.\n   * @return the image contents serialized as JSON.\n   */\n  public <T extends BuildableManifestTemplate> T getManifestTemplate(\n      Class<T> manifestTemplateClass, BlobDescriptor containerConfigurationBlobDescriptor) {\n    try {\n      // Set up the JSON template.\n      T template = manifestTemplateClass.getDeclaredConstructor().newInstance();\n\n      // Adds the container configuration reference.\n      DescriptorDigest containerConfigurationDigest =\n          containerConfigurationBlobDescriptor.getDigest();\n      long containerConfigurationSize = containerConfigurationBlobDescriptor.getSize();\n      template.setContainerConfiguration(containerConfigurationSize, containerConfigurationDigest);\n\n      // Adds the layers.\n      for (Layer layer : image.getLayers()) {\n        template.addLayer(\n            layer.getBlobDescriptor().getSize(), layer.getBlobDescriptor().getDigest());\n      }\n\n      return template;\n\n    } catch (InstantiationException\n        | IllegalAccessException\n        | NoSuchMethodException\n        | InvocationTargetException ex) {\n      throw new IllegalArgumentException(manifestTemplateClass + \" cannot be instantiated\", ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/JsonToImageTranslator.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.configuration.DockerHealthCheck;\nimport com.google.cloud.tools.jib.image.DigestOnlyLayer;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.LayerCountMismatchException;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.cloud.tools.jib.image.ReferenceLayer;\nimport com.google.cloud.tools.jib.image.ReferenceNoDiffIdLayer;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.Lists;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeParseException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\n\n/** Translates {@link V21ManifestTemplate} and {@link V22ManifestTemplate} into {@link Image}. */\npublic class JsonToImageTranslator {\n\n  /**\n   * Pattern used for parsing information out of exposed port configurations. Only accepts single\n   * ports with protocol.\n   *\n   * <p>Example matches: 100, 1000/tcp, 2000/udp\n   */\n  private static final Pattern PORT_PATTERN =\n      Pattern.compile(\"(?<portNum>\\\\d+)(?:/(?<protocol>tcp|udp))?\");\n\n  /**\n   * Pattern used for parsing environment variables in the format {@code NAME=VALUE}. {@code NAME}\n   * should not contain an '='.\n   *\n   * <p>Example matches: NAME=VALUE, A12345=$$$$$\n   */\n  @VisibleForTesting\n  static final Pattern ENVIRONMENT_PATTERN = Pattern.compile(\"(?<name>[^=]+)=(?<value>.*)\");\n\n  /**\n   * Translates {@link V21ManifestTemplate} to {@link Image}.\n   *\n   * @param manifestTemplate the template containing the image layers.\n   * @return the translated {@link Image}.\n   * @throws LayerPropertyNotFoundException if adding image layers fails.\n   * @throws BadContainerConfigurationFormatException if the container configuration is in a bad\n   *     format\n   */\n  public static Image toImage(V21ManifestTemplate manifestTemplate)\n      throws LayerPropertyNotFoundException, BadContainerConfigurationFormatException {\n    Image.Builder imageBuilder = Image.builder(V21ManifestTemplate.class);\n\n    // V21 layers are in reverse order of V22. (The first layer is the latest one.)\n    for (DescriptorDigest digest : Lists.reverse(manifestTemplate.getLayerDigests())) {\n      imageBuilder.addLayer(new DigestOnlyLayer(digest));\n    }\n\n    Optional<ContainerConfigurationTemplate> configuration =\n        manifestTemplate.getContainerConfiguration();\n    if (configuration.isPresent()) {\n      configureBuilderWithContainerConfiguration(imageBuilder, configuration.get());\n    }\n    return imageBuilder.build();\n  }\n\n  /**\n   * Translates {@link BuildableManifestTemplate} to {@link Image}. Uses the corresponding {@link\n   * ContainerConfigurationTemplate} to get the layer diff IDs.\n   *\n   * @param manifestTemplate the template containing the image layers.\n   * @param containerConfigurationTemplate the template containing the diff IDs and container\n   *     configuration properties.\n   * @return the translated {@link Image}.\n   * @throws LayerCountMismatchException if the manifest and configuration contain conflicting layer\n   *     information.\n   * @throws LayerPropertyNotFoundException if adding image layers fails.\n   * @throws BadContainerConfigurationFormatException if the container configuration is in a bad\n   *     format\n   */\n  public static Image toImage(\n      BuildableManifestTemplate manifestTemplate,\n      ContainerConfigurationTemplate containerConfigurationTemplate)\n      throws LayerCountMismatchException, LayerPropertyNotFoundException,\n          BadContainerConfigurationFormatException {\n    List<ReferenceNoDiffIdLayer> layers = new ArrayList<>();\n    for (BuildableManifestTemplate.ContentDescriptorTemplate layerObjectTemplate :\n        manifestTemplate.getLayers()) {\n      if (layerObjectTemplate.getDigest() == null) {\n        throw new IllegalArgumentException(\n            \"All layers in the manifest template must have digest set\");\n      }\n\n      layers.add(\n          new ReferenceNoDiffIdLayer(\n              new BlobDescriptor(layerObjectTemplate.getSize(), layerObjectTemplate.getDigest())));\n    }\n\n    List<DescriptorDigest> diffIds = containerConfigurationTemplate.getDiffIds();\n    if (layers.size() != diffIds.size()) {\n      throw new LayerCountMismatchException(\n          \"Mismatch between image manifest and container configuration\");\n    }\n\n    Image.Builder imageBuilder = Image.builder(manifestTemplate.getClass());\n\n    for (int layerIndex = 0; layerIndex < layers.size(); layerIndex++) {\n      ReferenceNoDiffIdLayer noDiffIdLayer = layers.get(layerIndex);\n      DescriptorDigest diffId = diffIds.get(layerIndex);\n\n      imageBuilder.addLayer(new ReferenceLayer(noDiffIdLayer.getBlobDescriptor(), diffId));\n    }\n\n    configureBuilderWithContainerConfiguration(imageBuilder, containerConfigurationTemplate);\n    return imageBuilder.build();\n  }\n\n  private static void configureBuilderWithContainerConfiguration(\n      Image.Builder imageBuilder, ContainerConfigurationTemplate containerConfigurationTemplate)\n      throws BadContainerConfigurationFormatException {\n\n    containerConfigurationTemplate.getHistory().forEach(imageBuilder::addHistory);\n\n    if (containerConfigurationTemplate.getCreated() != null) {\n      try {\n        imageBuilder.setCreated(Instant.parse(containerConfigurationTemplate.getCreated()));\n      } catch (DateTimeParseException ex) {\n        try {\n          // TODO: remove when using Java >= 12.\n          // See https://github.com/GoogleContainerTools/jib/issues/2428\n          imageBuilder.setCreated(\n              DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(\n                  containerConfigurationTemplate.getCreated(), Instant::from));\n        } catch (DateTimeParseException ignored) {\n          throw new BadContainerConfigurationFormatException(\n              \"Invalid image creation time: \" + containerConfigurationTemplate.getCreated(), ex);\n        }\n      }\n    }\n\n    if (containerConfigurationTemplate.getArchitecture() != null) {\n      imageBuilder.setArchitecture(containerConfigurationTemplate.getArchitecture());\n    }\n    if (containerConfigurationTemplate.getOs() != null) {\n      imageBuilder.setOs(containerConfigurationTemplate.getOs());\n    }\n\n    imageBuilder.setEntrypoint(containerConfigurationTemplate.getContainerEntrypoint());\n    imageBuilder.setProgramArguments(containerConfigurationTemplate.getContainerCmd());\n\n    List<String> baseHealthCheckCommand = containerConfigurationTemplate.getContainerHealthTest();\n    if (baseHealthCheckCommand != null) {\n      DockerHealthCheck.Builder builder = DockerHealthCheck.fromCommand(baseHealthCheckCommand);\n      if (containerConfigurationTemplate.getContainerHealthInterval() != null) {\n        builder.setInterval(\n            Duration.ofNanos(containerConfigurationTemplate.getContainerHealthInterval()));\n      }\n      if (containerConfigurationTemplate.getContainerHealthTimeout() != null) {\n        builder.setTimeout(\n            Duration.ofNanos(containerConfigurationTemplate.getContainerHealthTimeout()));\n      }\n      if (containerConfigurationTemplate.getContainerHealthStartPeriod() != null) {\n        builder.setStartPeriod(\n            Duration.ofNanos(containerConfigurationTemplate.getContainerHealthStartPeriod()));\n      }\n      if (containerConfigurationTemplate.getContainerHealthRetries() != null) {\n        builder.setRetries(containerConfigurationTemplate.getContainerHealthRetries());\n      }\n      imageBuilder.setHealthCheck(builder.build());\n    }\n\n    if (containerConfigurationTemplate.getContainerExposedPorts() != null) {\n      imageBuilder.addExposedPorts(\n          portMapToSet(containerConfigurationTemplate.getContainerExposedPorts()));\n    }\n\n    if (containerConfigurationTemplate.getContainerVolumes() != null) {\n      imageBuilder.addVolumes(volumeMapToSet(containerConfigurationTemplate.getContainerVolumes()));\n    }\n\n    if (containerConfigurationTemplate.getContainerEnvironment() != null) {\n      for (String environmentVariable : containerConfigurationTemplate.getContainerEnvironment()) {\n        Matcher matcher = ENVIRONMENT_PATTERN.matcher(environmentVariable);\n        if (!matcher.matches()) {\n          throw new BadContainerConfigurationFormatException(\n              \"Invalid environment variable definition: \" + environmentVariable);\n        }\n        imageBuilder.addEnvironmentVariable(matcher.group(\"name\"), matcher.group(\"value\"));\n      }\n    }\n\n    imageBuilder.addLabels(containerConfigurationTemplate.getContainerLabels());\n    imageBuilder.setWorkingDirectory(containerConfigurationTemplate.getContainerWorkingDir());\n    imageBuilder.setUser(containerConfigurationTemplate.getContainerUser());\n  }\n\n  /**\n   * Converts a map of exposed ports as strings to a set of {@link Port}s (e.g. {@code\n   * {\"1000/tcp\":{}}} -> {@code Port(1000, Protocol.TCP)}).\n   *\n   * @param portMap the map to convert\n   * @return a set of {@link Port}s\n   */\n  @VisibleForTesting\n  static ImmutableSet<Port> portMapToSet(@Nullable Map<String, Map<String, String>> portMap)\n      throws BadContainerConfigurationFormatException {\n    if (portMap == null) {\n      return ImmutableSet.of();\n    }\n    ImmutableSet.Builder<Port> ports = new ImmutableSet.Builder<>();\n    for (Map.Entry<String, Map<String, String>> entry : portMap.entrySet()) {\n      String port = entry.getKey();\n      Matcher matcher = PORT_PATTERN.matcher(port);\n      if (!matcher.matches()) {\n        throw new BadContainerConfigurationFormatException(\n            \"Invalid port configuration: '\" + port + \"'.\");\n      }\n\n      int portNumber = Integer.parseInt(matcher.group(\"portNum\"));\n      String protocol = matcher.group(\"protocol\");\n      ports.add(Port.parseProtocol(portNumber, protocol));\n    }\n    return ports.build();\n  }\n\n  /**\n   * Converts a map of volumes strings to a set of {@link AbsoluteUnixPath}s (e.g. {@code\n   * {\"/var/log/my-app-logs\":{}}} -> {@code AbsoluteUnixPath().get(\"/var/log/my-app-logs\")}).\n   *\n   * @param volumeMap the map to convert\n   * @return a set of {@link AbsoluteUnixPath}s\n   */\n  @VisibleForTesting\n  static ImmutableSet<AbsoluteUnixPath> volumeMapToSet(\n      @Nullable Map<String, Map<String, String>> volumeMap)\n      throws BadContainerConfigurationFormatException {\n    if (volumeMap == null) {\n      return ImmutableSet.of();\n    }\n\n    ImmutableSet.Builder<AbsoluteUnixPath> volumeList = ImmutableSet.builder();\n    for (String volume : volumeMap.keySet()) {\n      try {\n        volumeList.add(AbsoluteUnixPath.get(volume));\n      } catch (IllegalArgumentException exception) {\n        throw new BadContainerConfigurationFormatException(\"Invalid volume path: \" + volume);\n      }\n    }\n\n    return volumeList.build();\n  }\n\n  private JsonToImageTranslator() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestAndConfigTemplate.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonSubTypes;\nimport com.fasterxml.jackson.annotation.JsonTypeInfo;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport javax.annotation.Nullable;\n\n/** Stores a manifest and container config. */\npublic class ManifestAndConfigTemplate implements JsonTemplate {\n\n  @Nullable private String manifestDigest;\n\n  @JsonTypeInfo(\n      use = JsonTypeInfo.Id.CLASS,\n      include = JsonTypeInfo.As.PROPERTY,\n      property = \"@class\")\n  @JsonSubTypes({\n    @JsonSubTypes.Type(value = OciManifestTemplate.class),\n    @JsonSubTypes.Type(value = V21ManifestTemplate.class),\n    @JsonSubTypes.Type(value = V22ManifestTemplate.class),\n  })\n  @Nullable\n  private ManifestTemplate manifest;\n\n  @Nullable private ContainerConfigurationTemplate config;\n\n  @SuppressWarnings(\"unused\")\n  private ManifestAndConfigTemplate() {}\n\n  /**\n   * Creates an instance.\n   *\n   * @param manifest the image manifest\n   * @param config the container configuration\n   */\n  public ManifestAndConfigTemplate(\n      // TODO: switch to BuildableManifestTemplate after we stop supporting V21 manifest.\n      ManifestTemplate manifest,\n      // TODO: remove @Nullable after we stop supporting V21 manifest.\n      @Nullable ContainerConfigurationTemplate config) {\n    this(manifest, config, null);\n  }\n\n  /**\n   * Creates an instance.\n   *\n   * @param manifest the image manifest\n   * @param config the container configuration\n   * @param manifestDigest the digest of the manifest\n   */\n  public ManifestAndConfigTemplate(\n      // TODO: switch to BuildableManifestTemplate after we stop supporting V21 manifest.\n      ManifestTemplate manifest,\n      // TODO: remove @Nullable after we stop supporting V21 manifest.\n      @Nullable ContainerConfigurationTemplate config,\n      @Nullable String manifestDigest) {\n    this.manifest = manifest;\n    this.config = config;\n    this.manifestDigest = manifestDigest;\n  }\n\n  /**\n   * Gets the digest of the manifest.\n   *\n   * @return the digest\n   */\n  @Nullable\n  public String getManifestDigest() {\n    return manifestDigest;\n  }\n\n  /**\n   * Gets the manifest.\n   *\n   * @return the manifest\n   */\n  @Nullable\n  public ManifestTemplate getManifest() {\n    return manifest;\n  }\n\n  /**\n   * Gets the container configuration.\n   *\n   * @return the container configuration\n   */\n  @Nullable\n  public ContainerConfigurationTemplate getConfig() {\n    return config;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListGenerator.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.api.client.util.Preconditions;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport java.io.IOException;\nimport java.util.List;\n\n/** Generates a manifest list for {@link Image}s. */\npublic class ManifestListGenerator {\n\n  private final List<Image> images;\n\n  public ManifestListGenerator(List<Image> images) {\n    this.images = images;\n  }\n\n  /**\n   * Generates a manifest list JSON for the given {@link Image}s.\n   *\n   * @param <T> child type of {@link BuildableManifestTemplate}\n   * @param manifestTemplateClass the JSON template to translate the image to\n   * @return a manifest list JSON\n   * @throws IOException if generating a manifest list fails due to an I/O error when computing\n   *     digests\n   */\n  public <T extends BuildableManifestTemplate> ManifestTemplate getManifestListTemplate(\n      Class<T> manifestTemplateClass) throws IOException {\n    Preconditions.checkArgument(\n        manifestTemplateClass == V22ManifestTemplate.class,\n        \"Build an OCI image index is not yet supported\");\n    Preconditions.checkState(!images.isEmpty(), \"no images given\");\n\n    V22ManifestListTemplate manifestList = new V22ManifestListTemplate();\n    for (Image image : images) {\n      ImageToJsonTranslator imageTranslator = new ImageToJsonTranslator(image);\n\n      BlobDescriptor configDescriptor =\n          Digests.computeDigest(imageTranslator.getContainerConfiguration());\n\n      BuildableManifestTemplate manifestTemplate =\n          imageTranslator.getManifestTemplate(manifestTemplateClass, configDescriptor);\n      BlobDescriptor manifestDescriptor = Digests.computeDigest(manifestTemplate);\n\n      ManifestDescriptorTemplate manifest = new ManifestDescriptorTemplate();\n      manifest.setMediaType(manifestTemplate.getManifestMediaType());\n      manifest.setSize(manifestDescriptor.getSize());\n      manifest.setDigest(manifestDescriptor.getDigest().toString());\n      manifest.setPlatform(image.getArchitecture(), image.getOs());\n      manifestList.addManifest(manifest);\n    }\n    return manifestList;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListTemplate.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport java.util.List;\n\n/**\n * Parent class for manifest lists.\n *\n * @see V22ManifestListTemplate Docker V2.2 format\n * @see OciIndexTemplate OCI format\n */\npublic interface ManifestListTemplate extends ManifestTemplate {\n\n  /**\n   * Returns a list of digests for a specific platform found in the manifest list. see\n   * <a>https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list</a>\n   *\n   * @param architecture the architecture of the target platform\n   * @param os the os of the target platform\n   * @return a list of matching digests\n   */\n  List<String> getDigestsForPlatform(String architecture, String os);\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\n\n/** Parent class for image manifest and manifest list JSON templates. */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic interface ManifestTemplate extends JsonTemplate {\n\n  int getSchemaVersion();\n\n  String getManifestMediaType();\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/OciIndexTemplate.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\n/**\n * JSON template for OCI archive \"index.json\" file.\n *\n * <p>Example manifest JSON:\n *\n * <pre>{@code\n * {\n *   \"schemaVersion\": 2,\n *   \"mediaType\": \"application/vnd.oci.image.index.v1+json\",\n *   \"manifests\": [\n *     {\n *       \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n *       \"digest\": \"sha256:e684b1dceef404268f17d4adf7f755fd9912b8ae64864b3954a83ebb8aa628b3\",\n *       \"size\": 1132,\n *       \"platform\": {\n *         \"architecture\": \"ppc64le\",\n *         \"os\": \"linux\"\n *       },\n *       \"annotations\": {\n *         \"org.opencontainers.image.ref.name\": \"gcr.io/project/image:tag\"\n *       }\n *     }\n *   ]\n * }\n * }</pre>\n *\n * @see <a href=\"https://github.com/opencontainers/image-spec/blob/master/image-index.md\">OCI Image\n *     Index Specification</a>\n */\npublic class OciIndexTemplate implements ManifestListTemplate {\n\n  /** The OCI Index media type. */\n  public static final String MEDIA_TYPE = \"application/vnd.oci.image.index.v1+json\";\n\n  private final int schemaVersion = 2;\n  private final String mediaType = MEDIA_TYPE;\n\n  private final List<ManifestDescriptorTemplate> manifests = new ArrayList<>();\n\n  @Override\n  public int getSchemaVersion() {\n    return schemaVersion;\n  }\n\n  @Override\n  public String getManifestMediaType() {\n    return mediaType;\n  }\n\n  /**\n   * Adds a manifest reference with the given {@link BlobDescriptor}.\n   *\n   * @param descriptor the manifest blob descriptor\n   * @param imageReferenceName the image reference name\n   */\n  public void addManifest(BlobDescriptor descriptor, String imageReferenceName) {\n    ManifestDescriptorTemplate contentDescriptorTemplate =\n        new ManifestDescriptorTemplate(\n            OciManifestTemplate.MANIFEST_MEDIA_TYPE, descriptor.getSize(), descriptor.getDigest());\n    contentDescriptorTemplate.setAnnotations(\n        ImmutableMap.of(\"org.opencontainers.image.ref.name\", imageReferenceName));\n    manifests.add(contentDescriptorTemplate);\n  }\n\n  /**\n   * Adds a manifest.\n   *\n   * @param manifest a manifest descriptor\n   */\n  public void addManifest(OciIndexTemplate.ManifestDescriptorTemplate manifest) {\n    manifests.add(manifest);\n  }\n\n  @VisibleForTesting\n  public List<ManifestDescriptorTemplate> getManifests() {\n    return manifests;\n  }\n\n  @Override\n  public List<String> getDigestsForPlatform(String architecture, String os) {\n    return getManifests().stream()\n        .filter(\n            manifest ->\n                manifest.platform != null\n                    && os.equals(manifest.platform.os)\n                    && architecture.equals(manifest.platform.architecture))\n        .map(ManifestDescriptorTemplate::getDigest)\n        .filter(Objects::nonNull)\n        .map(DescriptorDigest::toString)\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Template for inner JSON object representing a single platform specific manifest. See <a\n   * href=\"https://github.com/opencontainers/image-spec/blob/main/image-index.md\">OCI Image Index\n   * Specification</a>\n   */\n  public static class ManifestDescriptorTemplate\n      extends BuildableManifestTemplate.ContentDescriptorTemplate {\n\n    ManifestDescriptorTemplate(String mediaType, long size, DescriptorDigest digest) {\n      super(mediaType, size, digest);\n    }\n\n    /** Necessary for Jackson to create from JSON. */\n    @SuppressWarnings(\"unused\")\n    private ManifestDescriptorTemplate() {\n      super();\n    }\n\n    @JsonIgnoreProperties(ignoreUnknown = true)\n    public static class Platform implements JsonTemplate {\n      @Nullable private String architecture;\n      @Nullable private String os;\n\n      @Nullable\n      public String getArchitecture() {\n        return architecture;\n      }\n\n      @Nullable\n      public String getOs() {\n        return os;\n      }\n    }\n\n    @Nullable private OciIndexTemplate.ManifestDescriptorTemplate.Platform platform;\n\n    /**\n     * Sets a platform.\n     *\n     * @param architecture the manifest architecture\n     * @param os the manifest os\n     */\n    public void setPlatform(String architecture, String os) {\n      platform = new OciIndexTemplate.ManifestDescriptorTemplate.Platform();\n      platform.architecture = architecture;\n      platform.os = os;\n    }\n\n    @Nullable\n    public OciIndexTemplate.ManifestDescriptorTemplate.Platform getPlatform() {\n      return platform;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/OciManifestTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/**\n * JSON Template for OCI Manifest Schema.\n *\n * <p>Example manifest JSON:\n *\n * <pre>{@code\n * {\n *   \"schemaVersion\": 2,\n *   \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n *   \"config\": {\n *     \"mediaType\": \"application/vnd.oci.image.config.v1+json\",\n *     \"size\": 631,\n *     \"digest\": \"sha256:26b84ca5b9050d32e68f66ad0f3e2bbcd247198a6e6e09a7effddf126eb8d873\"\n *   },\n *   \"layers\": [\n *     {\n *       \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n *       \"size\": 1991435,\n *       \"digest\": \"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"\n *     },\n *     {\n *       \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n *       \"size\": 32,\n *       \"digest\": \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\"\n *     }\n *   ]\n * }\n * }</pre>\n *\n * @see <a href=\"https://github.com/opencontainers/image-spec/blob/master/manifest.md\">OCI Image\n *     Manifest Specification</a>\n */\npublic class OciManifestTemplate implements BuildableManifestTemplate {\n\n  /** The OCI manifest media type. */\n  public static final String MANIFEST_MEDIA_TYPE = \"application/vnd.oci.image.manifest.v1+json\";\n\n  /** The OCI container configuration media type. */\n  private static final String CONTAINER_CONFIGURATION_MEDIA_TYPE =\n      \"application/vnd.oci.image.config.v1+json\";\n\n  /** The OCI layer media type. */\n  private static final String LAYER_MEDIA_TYPE = \"application/vnd.oci.image.layer.v1.tar+gzip\";\n\n  private final int schemaVersion = 2;\n\n  @SuppressWarnings(\"unused\")\n  private final String mediaType = MANIFEST_MEDIA_TYPE;\n\n  /** The container configuration reference. */\n  @Nullable private ContentDescriptorTemplate config;\n\n  /** The list of layer references. */\n  private final List<ContentDescriptorTemplate> layers = new ArrayList<>();\n\n  @Override\n  public int getSchemaVersion() {\n    return schemaVersion;\n  }\n\n  @Override\n  public String getManifestMediaType() {\n    return MANIFEST_MEDIA_TYPE;\n  }\n\n  @Override\n  @Nullable\n  public ContentDescriptorTemplate getContainerConfiguration() {\n    return config;\n  }\n\n  @Override\n  public List<ContentDescriptorTemplate> getLayers() {\n    return Collections.unmodifiableList(layers);\n  }\n\n  @Override\n  public void setContainerConfiguration(long size, DescriptorDigest digest) {\n    config = new ContentDescriptorTemplate(CONTAINER_CONFIGURATION_MEDIA_TYPE, size, digest);\n  }\n\n  @Override\n  public void addLayer(long size, DescriptorDigest digest) {\n    layers.add(new ContentDescriptorTemplate(LAYER_MEDIA_TYPE, size, digest));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/PlatformNotFoundInBaseImageException.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\n\n/** Exception thrown when build target platforms are not found in the base image. */\npublic class PlatformNotFoundInBaseImageException extends RegistryException {\n\n  public PlatformNotFoundInBaseImageException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/UnknownManifestFormatException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\n\n/** Exception thrown when trying to parse an unknown image manifest format. */\npublic class UnknownManifestFormatException extends RegistryException {\n\n  public UnknownManifestFormatException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/UnlistedPlatformInManifestListException.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\n\n/** Exception thrown when there is no matching platform in a manifest list. */\npublic class UnlistedPlatformInManifestListException extends RegistryException {\n\n  public UnlistedPlatformInManifestListException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V21ManifestTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * JSON template for Docker Manifest Schema V2.1\n *\n * <p>This is only for parsing manifests in the older V2.1 schema. Generated manifests should be in\n * the V2.2 schema using the {@link V22ManifestTemplate}.\n *\n * <p>Example manifest JSON (only the {@code fsLayers} and {@code history} fields are relevant for\n * parsing):\n *\n * <pre>{@code\n * {\n *   ...\n *   \"fsLayers\": {\n *     {\n *       \"blobSum\": \"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\"\n *     },\n *     {\n *       \"blobSum\": \"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\"\n *     }\n *   },\n *   \"history\": [\n *     {\n *       \"v1Compatibility\": \"<some manifest V1 JSON object>\"\n *     }\n *   ]\n *   ...\n * }\n * }</pre>\n *\n * @see <a href=\"https://docs.docker.com/registry/spec/manifest-v2-1/\">Image Manifest Version 2,\n *     Schema 1</a>\n */\npublic class V21ManifestTemplate implements ManifestTemplate {\n\n  public static final String MEDIA_TYPE = \"application/vnd.docker.distribution.manifest.v1+json\";\n\n  private final int schemaVersion = 1;\n  private final String mediaType = MEDIA_TYPE;\n\n  /** The list of layer references. */\n  private final List<LayerObjectTemplate> fsLayers = new ArrayList<>();\n\n  private final List<HistoryObjectTemplate> history = new ArrayList<>();\n\n  /**\n   * Template for inner JSON object representing a layer as part of the list of layer references.\n   */\n  @VisibleForTesting\n  static class LayerObjectTemplate implements JsonTemplate {\n\n    @Nullable private DescriptorDigest blobSum;\n\n    @Nullable\n    DescriptorDigest getDigest() {\n      return blobSum;\n    }\n  }\n\n  /** Template for inner JSON object representing history for a layer. */\n  private static class HistoryObjectTemplate implements JsonTemplate {\n\n    // The value is basically free-form; they may be structured differently in practice, e.g.,\n    // {\"architecture\": \"amd64\", \"config\": {\"User\": \"1001\", ...}, \"parent\": ...}\n    // {\"id\": ..., \"container_config\": {\"Cmd\":[\"\"]}}\n    @Nullable private String v1Compatibility;\n  }\n\n  /**\n   * Returns a list of descriptor digests for the layers in the image.\n   *\n   * @return a list of descriptor digests for the layers in the image.\n   */\n  public List<DescriptorDigest> getLayerDigests() {\n    List<DescriptorDigest> layerDigests = new ArrayList<>();\n\n    for (LayerObjectTemplate layerObjectTemplate : fsLayers) {\n      layerDigests.add(layerObjectTemplate.blobSum);\n    }\n\n    return layerDigests;\n  }\n\n  @Override\n  public int getSchemaVersion() {\n    return schemaVersion;\n  }\n\n  @Override\n  public String getManifestMediaType() {\n    return mediaType;\n  }\n\n  public List<LayerObjectTemplate> getFsLayers() {\n    return Collections.unmodifiableList(fsLayers);\n  }\n\n  /**\n   * Attempts to parse the container configuration JSON (of format {@code\n   * application/vnd.docker.container.image.v1+json}) from the {@code v1Compatibility} value of the\n   * first {@code history} entry, which corresponds to the latest layer.\n   *\n   * @return container configuration if the first history string holds it; {@code null} otherwise\n   */\n  public Optional<ContainerConfigurationTemplate> getContainerConfiguration() {\n    try {\n      if (history.isEmpty()) {\n        return Optional.empty();\n      }\n      String v1Compatibility = history.get(0).v1Compatibility;\n      if (v1Compatibility == null) {\n        return Optional.empty();\n      }\n\n      return Optional.of(\n          JsonTemplateMapper.readJson(v1Compatibility, ContainerConfigurationTemplate.class));\n    } catch (IOException ex) {\n      // not a container configuration; ignore and continue\n      return Optional.empty();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V22ManifestListTemplate.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\n\n/**\n * JSON Template for Docker Manifest List Schema V2.2\n *\n * <p>Example manifest list JSON:\n *\n * <pre>{@code\n * {\n *   \"schemaVersion\": 2,\n *   \"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\",\n *   \"manifests\": [\n *     {\n *       \"mediaType\": \"application/vnd.docker.image.manifest.v2+json\",\n *       \"size\": 7143,\n *       \"digest\": \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n *       \"platform\": {\n *         \"architecture\": \"ppc64le\",\n *         \"os\": \"linux\",\n *       }\n *     },\n *     {\n *       \"mediaType\": \"application/vnd.docker.image.manifest.v2+json\",\n *       \"size\": 7682,\n *       \"digest\": \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n *       \"platform\": {\n *         \"architecture\": \"amd64\",\n *         \"os\": \"linux\",\n *         \"features\": [\n *           \"sse4\"\n *         ]\n *       }\n *     }\n *   ]\n * }\n * }</pre>\n *\n * @see <a href=\"https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list\">Image Manifest\n *     Version 2, Schema 2: Manifest List</a>\n */\npublic class V22ManifestListTemplate implements ManifestListTemplate {\n\n  public static final String MANIFEST_MEDIA_TYPE =\n      \"application/vnd.docker.distribution.manifest.list.v2+json\";\n  private static final int SCHEMA_VERSION = 2;\n\n  private final int schemaVersion = SCHEMA_VERSION;\n  private final String mediaType = MANIFEST_MEDIA_TYPE;\n\n  @Override\n  public int getSchemaVersion() {\n    return schemaVersion;\n  }\n\n  @Override\n  public String getManifestMediaType() {\n    return mediaType;\n  }\n\n  @Nullable private List<ManifestDescriptorTemplate> manifests;\n\n  /**\n   * Adds a manifest.\n   *\n   * @param manifest a manifest descriptor\n   */\n  public void addManifest(ManifestDescriptorTemplate manifest) {\n    if (manifests == null) {\n      manifests = new ArrayList<>();\n    }\n    manifests.add(manifest);\n  }\n\n  @VisibleForTesting\n  public List<ManifestDescriptorTemplate> getManifests() {\n    return Preconditions.checkNotNull(manifests);\n  }\n\n  @Override\n  public List<String> getDigestsForPlatform(String architecture, String os) {\n    return getManifests().stream()\n        .filter(\n            manifest ->\n                manifest.platform != null\n                    && os.equals(manifest.platform.os)\n                    && architecture.equals(manifest.platform.architecture))\n        .map(ManifestDescriptorTemplate::getDigest)\n        .collect(Collectors.toList());\n  }\n\n  /** Template for inner JSON object representing a single platform specific manifest. */\n  public static class ManifestDescriptorTemplate implements JsonTemplate {\n\n    @JsonIgnoreProperties(ignoreUnknown = true)\n    public static class Platform implements JsonTemplate {\n      @Nullable private String architecture;\n      @Nullable private String os;\n\n      @Nullable\n      public String getArchitecture() {\n        return architecture;\n      }\n\n      @Nullable\n      public String getOs() {\n        return os;\n      }\n    }\n\n    @Nullable private String mediaType;\n    @Nullable private String digest;\n\n    @SuppressWarnings(\"unused\")\n    private long size;\n\n    @Nullable private Platform platform;\n\n    public void setSize(long size) {\n      this.size = size;\n    }\n\n    public void setDigest(String digest) {\n      this.digest = digest;\n    }\n\n    @Nullable\n    public String getDigest() {\n      return digest;\n    }\n\n    public void setMediaType(String mediaType) {\n      this.mediaType = mediaType;\n    }\n\n    @Nullable\n    public String getMediaType() {\n      return mediaType;\n    }\n\n    /**\n     * Sets a platform.\n     *\n     * @param architecture the manifest architecture\n     * @param os the manifest os\n     */\n    public void setPlatform(String architecture, String os) {\n      platform = new Platform();\n      platform.architecture = architecture;\n      platform.os = os;\n    }\n\n    @Nullable\n    public Platform getPlatform() {\n      return platform;\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V22ManifestTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/**\n * JSON Template for Docker Manifest Schema V2.2\n *\n * <p>Example manifest JSON:\n *\n * <pre>{@code\n * {\n *   \"schemaVersion\": 2,\n *   \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\",\n *   \"config\": {\n *     \"mediaType\": \"application/vnd.docker.container.image.v1+json\",\n *     \"size\": 631,\n *     \"digest\": \"sha256:26b84ca5b9050d32e68f66ad0f3e2bbcd247198a6e6e09a7effddf126eb8d873\"\n *   },\n *   \"layers\": [\n *     {\n *       \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n *       \"size\": 1991435,\n *       \"digest\": \"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"\n *     },\n *     {\n *       \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n *       \"size\": 32,\n *       \"digest\": \"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4\"\n *     }\n *   ]\n * }\n * }</pre>\n *\n * @see <a href=\"https://docs.docker.com/registry/spec/manifest-v2-2/\">Image Manifest Version 2,\n *     Schema 2</a>\n */\npublic class V22ManifestTemplate implements BuildableManifestTemplate {\n\n  /** The Docker V2.2 manifest media type. */\n  public static final String MANIFEST_MEDIA_TYPE =\n      \"application/vnd.docker.distribution.manifest.v2+json\";\n\n  /** The Docker V2.2 container configuration media type. */\n  private static final String CONTAINER_CONFIGURATION_MEDIA_TYPE =\n      \"application/vnd.docker.container.image.v1+json\";\n\n  /** The Docker V2.2 layer media type. */\n  private static final String LAYER_MEDIA_TYPE =\n      \"application/vnd.docker.image.rootfs.diff.tar.gzip\";\n\n  private final int schemaVersion = 2;\n\n  @SuppressWarnings(\"unused\")\n  private final String mediaType = MANIFEST_MEDIA_TYPE;\n\n  /** The container configuration reference. */\n  @Nullable private ContentDescriptorTemplate config;\n\n  /** The list of layer references. */\n  private final List<ContentDescriptorTemplate> layers = new ArrayList<>();\n\n  @Override\n  public int getSchemaVersion() {\n    return schemaVersion;\n  }\n\n  @Override\n  public String getManifestMediaType() {\n    return MANIFEST_MEDIA_TYPE;\n  }\n\n  @Override\n  @Nullable\n  public ContentDescriptorTemplate getContainerConfiguration() {\n    return config;\n  }\n\n  @Override\n  public List<ContentDescriptorTemplate> getLayers() {\n    return Collections.unmodifiableList(layers);\n  }\n\n  @Override\n  public void setContainerConfiguration(long size, DescriptorDigest digest) {\n    config = new ContentDescriptorTemplate(CONTAINER_CONFIGURATION_MEDIA_TYPE, size, digest);\n  }\n\n  @Override\n  public void addLayer(long size, DescriptorDigest digest) {\n    layers.add(new ContentDescriptorTemplate(LAYER_MEDIA_TYPE, size, digest));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/json/JsonTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.json;\n\nimport com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.annotation.JsonInclude;\n\n/**\n * All JSON templates to be used with {@link JsonTemplateMapper} must extend this class.\n *\n * <p>Json fields should be private fields and fields that are {@code null} will not be serialized.\n */\n@JsonInclude(JsonInclude.Include.NON_NULL)\n@JsonAutoDetect(\n    fieldVisibility = JsonAutoDetect.Visibility.ANY,\n    getterVisibility = JsonAutoDetect.Visibility.NONE,\n    isGetterVisibility = JsonAutoDetect.Visibility.NONE,\n    setterVisibility = JsonAutoDetect.Visibility.NONE,\n    creatorVisibility = JsonAutoDetect.Visibility.NONE)\npublic interface JsonTemplate {}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/json/JsonTemplateMapper.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.json;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.type.CollectionType;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.channels.Channels;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardOpenOption;\nimport java.util.List;\n\n// TODO: Add JsonFactory for HTTP response parsing.\n/**\n * Helper class for serializing and deserializing JSON.\n *\n * <p>The interface uses Jackson as the JSON parser. Some useful annotations to include on classes\n * used as templates for JSON are:\n *\n * <p>{@code @JsonInclude(JsonInclude.Include.NON_NULL)}\n *\n * <ul>\n *   <li>Does not serialize fields that are {@code null}.\n * </ul>\n *\n * {@code @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)}\n *\n * <ul>\n *   <li>Fields that are private are also accessible for serialization/deserialization.\n * </ul>\n *\n * @see <a href=\"https://github.com/FasterXML/jackson\">https://github.com/FasterXML/jackson</a>\n */\npublic class JsonTemplateMapper {\n\n  private static final ObjectMapper objectMapper =\n      new ObjectMapper().registerModule(new JavaTimeModule());\n\n  /**\n   * Deserializes a JSON file via a JSON object template.\n   *\n   * @param <T> child type of {@link JsonTemplate}\n   * @param jsonFile a file containing a JSON string\n   * @param templateClass the template to deserialize the string to\n   * @return the template filled with the values parsed from {@code jsonFile}\n   * @throws IOException if an error occurred during reading the file or parsing the JSON\n   */\n  public static <T extends JsonTemplate> T readJsonFromFile(Path jsonFile, Class<T> templateClass)\n      throws IOException {\n    try (InputStream fileIn = Files.newInputStream(jsonFile)) {\n      return objectMapper.readValue(fileIn, templateClass);\n    }\n  }\n\n  /**\n   * Deserializes a JSON file via a JSON object template with a shared lock on the file.\n   *\n   * @param <T> child type of {@link JsonTemplate}\n   * @param jsonFile a file containing a JSON string\n   * @param templateClass the template to deserialize the string to\n   * @return the template filled with the values parsed from {@code jsonFile}\n   * @throws IOException if an error occurred during reading the file or parsing the JSON\n   */\n  public static <T extends JsonTemplate> T readJsonFromFileWithLock(\n      Path jsonFile, Class<T> templateClass) throws IOException {\n    // channel is closed by inputStream.close()\n    FileChannel channel = FileChannel.open(jsonFile, StandardOpenOption.READ);\n    channel.lock(0, Long.MAX_VALUE, true); // shared lock, released by channel close\n    try (InputStream inputStream = Channels.newInputStream(channel)) {\n      return objectMapper.readValue(inputStream, templateClass);\n    }\n  }\n\n  /**\n   * Deserializes a JSON object from a JSON input stream.\n   *\n   * @param <T> child type of {@link JsonTemplate}\n   * @param jsonStream input stream\n   * @param templateClass the template to deserialize the string to\n   * @return the template filled with the values parsed from {@code jsonString}\n   * @throws IOException if an error occurred during parsing the JSON\n   */\n  public static <T extends JsonTemplate> T readJson(InputStream jsonStream, Class<T> templateClass)\n      throws IOException {\n    return objectMapper.readValue(jsonStream, templateClass);\n  }\n\n  /**\n   * Deserializes a JSON object from a JSON string.\n   *\n   * @param <T> child type of {@link JsonTemplate}\n   * @param jsonString a JSON string\n   * @param templateClass the template to deserialize the string to\n   * @return the template filled with the values parsed from {@code jsonString}\n   * @throws IOException if an error occurred during parsing the JSON\n   */\n  public static <T extends JsonTemplate> T readJson(String jsonString, Class<T> templateClass)\n      throws IOException {\n    return objectMapper.readValue(jsonString, templateClass);\n  }\n\n  /**\n   * Deserializes a JSON object from a JSON byte array.\n   *\n   * @param <T> child type of {@link JsonTemplate}\n   * @param jsonBytes a JSON byte array\n   * @param templateClass the template to deserialize the string to\n   * @return the template filled with the values parsed from {@code jsonBytes}\n   * @throws IOException if an error occurred during parsing the JSON\n   */\n  public static <T extends JsonTemplate> T readJson(byte[] jsonBytes, Class<T> templateClass)\n      throws IOException {\n    return objectMapper.readValue(jsonBytes, templateClass);\n  }\n\n  /**\n   * Deserializes a JSON object list from a JSON string.\n   *\n   * @param <T> child type of {@link JsonTemplate}\n   * @param jsonString a JSON string\n   * @param templateClass the template to deserialize the string to\n   * @return the template filled with the values parsed from {@code jsonString}\n   * @throws IOException if an error occurred during parsing the JSON\n   */\n  public static <T extends JsonTemplate> List<T> readListOfJson(\n      String jsonString, Class<T> templateClass) throws IOException {\n    CollectionType listType =\n        objectMapper.getTypeFactory().constructCollectionType(List.class, templateClass);\n    return objectMapper.readValue(jsonString, listType);\n  }\n\n  public static String toUtf8String(JsonTemplate template) throws IOException {\n    return toUtf8String((Object) template);\n  }\n\n  public static String toUtf8String(List<? extends JsonTemplate> templates) throws IOException {\n    return toUtf8String((Object) templates);\n  }\n\n  public static byte[] toByteArray(JsonTemplate template) throws IOException {\n    return toByteArray((Object) template);\n  }\n\n  public static byte[] toByteArray(List<? extends JsonTemplate> templates) throws IOException {\n    return toByteArray((Object) templates);\n  }\n\n  public static void writeTo(JsonTemplate template, OutputStream out) throws IOException {\n    writeTo((Object) template, out);\n  }\n\n  public static void writeTo(List<? extends JsonTemplate> templates, OutputStream out)\n      throws IOException {\n    writeTo((Object) templates, out);\n  }\n\n  private static String toUtf8String(Object template) throws IOException {\n    return new String(toByteArray(template), StandardCharsets.UTF_8);\n  }\n\n  private static byte[] toByteArray(Object template) throws IOException {\n    ByteArrayOutputStream out = new ByteArrayOutputStream();\n    writeTo(template, out);\n    return out.toByteArray();\n  }\n\n  private static void writeTo(Object template, OutputStream out) throws IOException {\n    objectMapper.writeValue(out, template);\n  }\n\n  private JsonTemplateMapper() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/AbstractManifestPuller.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.google.api.client.http.HttpMethods;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.UnknownManifestFormatException;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** Base class for manifest pullers. */\nabstract class AbstractManifestPuller<T extends ManifestTemplate, R>\n    implements RegistryEndpointProvider<R> {\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  private final String imageQualifier;\n  private final Class<T> manifestTemplateClass;\n\n  AbstractManifestPuller(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      String imageQualifier,\n      Class<T> manifestTemplateClass) {\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.imageQualifier = imageQualifier;\n    this.manifestTemplateClass = manifestTemplateClass;\n  }\n\n  @Nullable\n  @Override\n  public BlobHttpContent getContent() {\n    return null;\n  }\n\n  @Override\n  public List<String> getAccept() {\n    if (manifestTemplateClass.equals(V21ManifestTemplate.class)) {\n      return Collections.singletonList(V21ManifestTemplate.MEDIA_TYPE);\n    }\n    if (manifestTemplateClass.equals(V22ManifestTemplate.class)) {\n      return Collections.singletonList(V22ManifestTemplate.MANIFEST_MEDIA_TYPE);\n    }\n    if (manifestTemplateClass.equals(OciManifestTemplate.class)) {\n      return Collections.singletonList(OciManifestTemplate.MANIFEST_MEDIA_TYPE);\n    }\n    if (manifestTemplateClass.equals(V22ManifestListTemplate.class)) {\n      return Collections.singletonList(V22ManifestListTemplate.MANIFEST_MEDIA_TYPE);\n    }\n    if (manifestTemplateClass.equals(OciIndexTemplate.class)) {\n      return Collections.singletonList(OciIndexTemplate.MEDIA_TYPE);\n    }\n\n    return Arrays.asList(\n        OciManifestTemplate.MANIFEST_MEDIA_TYPE,\n        V22ManifestTemplate.MANIFEST_MEDIA_TYPE,\n        V21ManifestTemplate.MEDIA_TYPE,\n        V22ManifestListTemplate.MANIFEST_MEDIA_TYPE,\n        OciIndexTemplate.MEDIA_TYPE);\n  }\n\n  /** Parses the response body into a {@link ManifestAndDigest}. */\n  @Override\n  public R handleResponse(Response response) throws IOException, UnknownManifestFormatException {\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    DescriptorDigest digest =\n        Digests.computeDigest(response.getBody(), byteArrayOutputStream).getDigest();\n    String jsonString = byteArrayOutputStream.toString(StandardCharsets.UTF_8.name());\n    T manifestTemplate = getManifestTemplateFromJson(jsonString);\n    return computeReturn(new ManifestAndDigest<>(manifestTemplate, digest));\n  }\n\n  abstract R computeReturn(ManifestAndDigest<T> manifestAndDigest);\n\n  @Override\n  public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n    return new URL(\n        apiRouteBase\n            + registryEndpointRequestProperties.getImageName()\n            + \"/manifests/\"\n            + imageQualifier);\n  }\n\n  @Override\n  public String getHttpMethod() {\n    return HttpMethods.GET;\n  }\n\n  @Override\n  public String getActionDescription() {\n    return \"pull image manifest for \"\n        + registryEndpointRequestProperties.getServerUrl()\n        + \"/\"\n        + registryEndpointRequestProperties.getImageName()\n        + \":\"\n        + imageQualifier;\n  }\n\n  /**\n   * Instantiates a {@link ManifestTemplate} from a JSON string. This checks the {@code\n   * schemaVersion} field of the JSON to determine which manifest version to use.\n   */\n  private T getManifestTemplateFromJson(String jsonString)\n      throws IOException, UnknownManifestFormatException {\n    ObjectNode node = new ObjectMapper().readValue(jsonString, ObjectNode.class);\n    if (!node.has(\"schemaVersion\")) {\n      throw new UnknownManifestFormatException(\"Cannot find field 'schemaVersion' in manifest\");\n    }\n\n    int schemaVersion = node.get(\"schemaVersion\").asInt(-1);\n    if (schemaVersion == -1) {\n      throw new UnknownManifestFormatException(\"'schemaVersion' field is not an integer\");\n    }\n\n    if (schemaVersion == 1) {\n      return manifestTemplateClass.cast(\n          JsonTemplateMapper.readJson(jsonString, V21ManifestTemplate.class));\n    }\n    if (schemaVersion == 2) {\n      // 'schemaVersion' of 2 can be either Docker V2.2 or OCI.\n      JsonNode mediaTypeNode = node.get(\"mediaType\");\n      if (mediaTypeNode == null) { // not Docker, hence OCI\n        if (node.get(\"manifests\") != null) {\n          return manifestTemplateClass.cast(\n              JsonTemplateMapper.readJson(jsonString, OciIndexTemplate.class));\n        }\n        if (node.get(\"config\") != null) {\n          return manifestTemplateClass.cast(\n              JsonTemplateMapper.readJson(jsonString, OciManifestTemplate.class));\n        }\n        throw new UnknownManifestFormatException(\n            \"'schemaVersion' is 2, but neither 'manifests' nor 'config' exists\");\n      }\n\n      String mediaType = mediaTypeNode.asText();\n      if (OciManifestTemplate.MANIFEST_MEDIA_TYPE.equals(mediaType)) {\n        return manifestTemplateClass.cast(\n            JsonTemplateMapper.readJson(jsonString, OciManifestTemplate.class));\n      }\n      if (V22ManifestTemplate.MANIFEST_MEDIA_TYPE.equals(mediaType)) {\n        return manifestTemplateClass.cast(\n            JsonTemplateMapper.readJson(jsonString, V22ManifestTemplate.class));\n      }\n      if (V22ManifestListTemplate.MANIFEST_MEDIA_TYPE.equals(mediaType)) {\n        return manifestTemplateClass.cast(\n            JsonTemplateMapper.readJson(jsonString, V22ManifestListTemplate.class));\n      }\n      if (OciIndexTemplate.MEDIA_TYPE.equals(mediaType)) {\n        return manifestTemplateClass.cast(\n            JsonTemplateMapper.readJson(jsonString, OciIndexTemplate.class));\n      }\n      throw new UnknownManifestFormatException(\"Unknown mediaType: \" + mediaType);\n    }\n    throw new UnknownManifestFormatException(\n        \"Unknown schemaVersion: \" + schemaVersion + \" - only 1 and 2 are supported\");\n  }\n\n  @Override\n  public R handleHttpResponseException(ResponseException responseException)\n      throws ResponseException, RegistryErrorException {\n    throw responseException;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpMethods;\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/** Retrieves the {@code WWW-Authenticate} header from the registry API. */\nclass AuthenticationMethodRetriever\n    implements RegistryEndpointProvider<Optional<RegistryAuthenticator>> {\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  @Nullable private final String userAgent;\n  private final FailoverHttpClient httpClient;\n\n  AuthenticationMethodRetriever(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      @Nullable String userAgent,\n      FailoverHttpClient httpClient) {\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.userAgent = userAgent;\n    this.httpClient = httpClient;\n  }\n\n  @Nullable\n  @Override\n  public BlobHttpContent getContent() {\n    return null;\n  }\n\n  @Override\n  public List<String> getAccept() {\n    return Collections.emptyList();\n  }\n\n  /**\n   * The request did not error, meaning that the registry does not require authentication.\n   *\n   * @param response ignored\n   * @return {@link Optional#empty()}\n   */\n  @Override\n  public Optional<RegistryAuthenticator> handleResponse(Response response) {\n    return Optional.empty();\n  }\n\n  @Override\n  public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n    return new URL(apiRouteBase);\n  }\n\n  @Override\n  public String getHttpMethod() {\n    return HttpMethods.GET;\n  }\n\n  @Override\n  public String getActionDescription() {\n    return \"retrieve authentication method for \" + registryEndpointRequestProperties.getServerUrl();\n  }\n\n  @Override\n  public Optional<RegistryAuthenticator> handleHttpResponseException(\n      ResponseException responseException) throws ResponseException, RegistryErrorException {\n    // Only valid for status code of '401 Unauthorized'.\n    if (responseException.getStatusCode() != HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) {\n      throw responseException;\n    }\n\n    // Checks if the 'WWW-Authenticate' header is present.\n    String authenticationMethod = responseException.getHeaders().getAuthenticate();\n    if (authenticationMethod == null) {\n      throw new RegistryErrorExceptionBuilder(getActionDescription(), responseException)\n          .addReason(\"'WWW-Authenticate' header not found\")\n          .build();\n    }\n\n    // Parses the header to retrieve the components.\n    try {\n      return RegistryAuthenticator.fromAuthenticationMethod(\n          authenticationMethod, registryEndpointRequestProperties, userAgent, httpClient);\n\n    } catch (RegistryAuthenticationFailedException ex) {\n      throw new RegistryErrorExceptionBuilder(getActionDescription(), ex)\n          .addReason(\"Failed get authentication method from 'WWW-Authenticate' header\")\n          .build();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobChecker.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpMethods;\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * Checks if an image's BLOB exists on a registry, and retrieves its {@link BlobDescriptor} if it\n * exists.\n */\nclass BlobChecker implements RegistryEndpointProvider<Optional<BlobDescriptor>> {\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  private final DescriptorDigest blobDigest;\n\n  BlobChecker(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      DescriptorDigest blobDigest) {\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.blobDigest = blobDigest;\n  }\n\n  /** Returns the BLOB's content descriptor. */\n  @Override\n  public Optional<BlobDescriptor> handleResponse(Response response) throws RegistryErrorException {\n    long contentLength = response.getContentLength();\n    if (contentLength < 0) {\n      throw new RegistryErrorExceptionBuilder(getActionDescription())\n          .addReason(\"Did not receive Content-Length header\")\n          .build();\n    }\n\n    return Optional.of(new BlobDescriptor(contentLength, blobDigest));\n  }\n\n  @Override\n  public Optional<BlobDescriptor> handleHttpResponseException(ResponseException responseException)\n      throws ResponseException {\n    if (responseException.getStatusCode() != HttpStatusCodes.STATUS_CODE_NOT_FOUND) {\n      throw responseException;\n    }\n\n    if (responseException.getContent() == null) {\n      // TODO: The Google HTTP client gives null content for HEAD requests. Make the content never\n      // be null, even for HEAD requests.\n      return Optional.empty();\n    }\n\n    // Find a BLOB_UNKNOWN error response code.\n    ErrorCodes errorCode = ErrorResponseUtil.getErrorCode(responseException);\n    if (errorCode == ErrorCodes.BLOB_UNKNOWN) {\n      return Optional.empty();\n    }\n\n    // BLOB_UNKNOWN was not found as a error response code.\n    throw responseException;\n  }\n\n  @Override\n  public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n    return new URL(\n        apiRouteBase + registryEndpointRequestProperties.getImageName() + \"/blobs/\" + blobDigest);\n  }\n\n  @Nullable\n  @Override\n  public BlobHttpContent getContent() {\n    return null;\n  }\n\n  @Override\n  public List<String> getAccept() {\n    return Collections.emptyList();\n  }\n\n  @Override\n  public String getHttpMethod() {\n    return HttpMethods.HEAD;\n  }\n\n  @Override\n  public String getActionDescription() {\n    return \"check BLOB exists for \"\n        + registryEndpointRequestProperties.getServerUrl()\n        + \"/\"\n        + registryEndpointRequestProperties.getImageName()\n        + \" with digest \"\n        + blobDigest;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobPuller.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpMethods;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.NotifyingOutputStream;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\n\n/** Pulls an image's BLOB (layer or container configuration). */\nclass BlobPuller implements RegistryEndpointProvider<Void> {\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n\n  /** The digest of the BLOB to pull. */\n  private final DescriptorDigest blobDigest;\n\n  /**\n   * The {@link OutputStream} to write the BLOB to. Closes the {@link OutputStream} after writing.\n   */\n  private final OutputStream destinationOutputStream;\n\n  private final Consumer<Long> blobSizeListener;\n  private final Consumer<Long> writtenByteCountListener;\n\n  BlobPuller(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      DescriptorDigest blobDigest,\n      OutputStream destinationOutputStream,\n      Consumer<Long> blobSizeListener,\n      Consumer<Long> writtenByteCountListener) {\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.blobDigest = blobDigest;\n    this.destinationOutputStream = destinationOutputStream;\n    this.blobSizeListener = blobSizeListener;\n    this.writtenByteCountListener = writtenByteCountListener;\n  }\n\n  @Override\n  public Void handleResponse(Response response) throws IOException, UnexpectedBlobDigestException {\n    blobSizeListener.accept(response.getContentLength());\n\n    try (OutputStream outputStream =\n        new NotifyingOutputStream(destinationOutputStream, writtenByteCountListener)) {\n      BlobDescriptor receivedBlobDescriptor =\n          Digests.computeDigest(response.getBody(), outputStream);\n\n      if (!blobDigest.equals(receivedBlobDescriptor.getDigest())) {\n        throw new UnexpectedBlobDigestException(\n            \"The pulled BLOB has digest '\"\n                + receivedBlobDescriptor.getDigest()\n                + \"', but the request digest was '\"\n                + blobDigest\n                + \"'\");\n      }\n    }\n\n    return null;\n  }\n\n  @Override\n  @Nullable\n  public BlobHttpContent getContent() {\n    return null;\n  }\n\n  @Override\n  public List<String> getAccept() {\n    return Collections.emptyList();\n  }\n\n  @Override\n  public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n    return new URL(\n        apiRouteBase + registryEndpointRequestProperties.getImageName() + \"/blobs/\" + blobDigest);\n  }\n\n  @Override\n  public String getHttpMethod() {\n    return HttpMethods.GET;\n  }\n\n  @Override\n  public String getActionDescription() {\n    return \"pull BLOB for \"\n        + registryEndpointRequestProperties.getServerUrl()\n        + \"/\"\n        + registryEndpointRequestProperties.getImageName()\n        + \" with digest \"\n        + blobDigest;\n  }\n\n  @Override\n  public Void handleHttpResponseException(ResponseException responseException)\n      throws ResponseException, RegistryErrorException {\n    throw responseException;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobPusher.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.GenericUrl;\nimport com.google.api.client.http.HttpMethods;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.common.net.MediaType;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\n\n/**\n * Pushes an image's BLOB (layer or container configuration).\n *\n * <p>The BLOB is pushed in three stages:\n *\n * <ol>\n *   <li>Initialize - Gets a location back to write the BLOB content to\n *   <li>Write BLOB - Write the BLOB content to the received location\n *   <li>Commit BLOB - Commits the BLOB with its digest\n * </ol>\n */\nclass BlobPusher {\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  private final DescriptorDigest blobDigest;\n  private final Blob blob;\n  @Nullable private final String sourceRepository;\n\n  /** Initializes the BLOB upload. */\n  private class Initializer implements RegistryEndpointProvider<Optional<URL>> {\n\n    @Nullable\n    @Override\n    public BlobHttpContent getContent() {\n      return null;\n    }\n\n    @Override\n    public List<String> getAccept() {\n      return Collections.emptyList();\n    }\n\n    /**\n     * Returns a URL to continue pushing the BLOB to, or {@link Optional#empty()} if the BLOB\n     * already exists on the registry.\n     */\n    @Override\n    public Optional<URL> handleResponse(Response response) throws RegistryErrorException {\n      switch (response.getStatusCode()) {\n        case HttpURLConnection.HTTP_CREATED:\n          // The BLOB exists in the registry.\n          return Optional.empty();\n\n        case HttpURLConnection.HTTP_ACCEPTED:\n          return Optional.of(getRedirectLocation(response));\n\n        default:\n          throw buildRegistryErrorException(\n              \"Received unrecognized status code \" + response.getStatusCode());\n      }\n    }\n\n    @Override\n    public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n      StringBuilder url =\n          new StringBuilder(apiRouteBase)\n              .append(registryEndpointRequestProperties.getImageName())\n              .append(\"/blobs/uploads/\");\n      if (sourceRepository != null) {\n        url.append(\"?mount=\").append(blobDigest).append(\"&from=\").append(sourceRepository);\n      }\n\n      return new URL(url.toString());\n    }\n\n    @Override\n    public String getHttpMethod() {\n      return HttpMethods.POST;\n    }\n\n    @Override\n    public String getActionDescription() {\n      return BlobPusher.this.getActionDescription();\n    }\n\n    @Override\n    public Optional<URL> handleHttpResponseException(ResponseException responseException)\n        throws ResponseException, RegistryErrorException {\n      throw responseException;\n    }\n  }\n\n  /** Writes the BLOB content to the upload location. */\n  private class Writer implements RegistryEndpointProvider<URL> {\n\n    private final URL location;\n    private final Consumer<Long> writtenByteCountListener;\n\n    @Nullable\n    @Override\n    public BlobHttpContent getContent() {\n      return new BlobHttpContent(blob, MediaType.OCTET_STREAM.toString(), writtenByteCountListener);\n    }\n\n    @Override\n    public List<String> getAccept() {\n      return Collections.emptyList();\n    }\n\n    /** Returns a URL to continue pushing the BLOB to. */\n    @Override\n    public URL handleResponse(Response response) throws RegistryException {\n      // TODO: Handle 204 No Content\n      return getRedirectLocation(response);\n    }\n\n    @Override\n    public URL getApiRoute(String apiRouteBase) {\n      return location;\n    }\n\n    @Override\n    public String getHttpMethod() {\n      return HttpMethods.PATCH;\n    }\n\n    @Override\n    public String getActionDescription() {\n      return BlobPusher.this.getActionDescription();\n    }\n\n    private Writer(URL location, Consumer<Long> writtenByteCountListener) {\n      this.location = location;\n      this.writtenByteCountListener = writtenByteCountListener;\n    }\n\n    @Override\n    public URL handleHttpResponseException(ResponseException responseException)\n        throws ResponseException, RegistryErrorException {\n      throw responseException;\n    }\n  }\n\n  /** Commits the written BLOB. */\n  private class Committer implements RegistryEndpointProvider<Void> {\n\n    private final URL location;\n\n    @Nullable\n    @Override\n    public BlobHttpContent getContent() {\n      return null;\n    }\n\n    @Override\n    public List<String> getAccept() {\n      return Collections.emptyList();\n    }\n\n    @Override\n    public Void handleResponse(Response response) {\n      return null;\n    }\n\n    /** Returns {@code location} with query parameter 'digest' set to the BLOB's digest. */\n    @Override\n    public URL getApiRoute(String apiRouteBase) {\n      return new GenericUrl(location).set(\"digest\", blobDigest).toURL();\n    }\n\n    @Override\n    public String getHttpMethod() {\n      return HttpMethods.PUT;\n    }\n\n    @Override\n    public String getActionDescription() {\n      return BlobPusher.this.getActionDescription();\n    }\n\n    private Committer(URL location) {\n      this.location = location;\n    }\n\n    @Override\n    public Void handleHttpResponseException(ResponseException responseException)\n        throws ResponseException, RegistryErrorException {\n      throw responseException;\n    }\n  }\n\n  BlobPusher(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      DescriptorDigest blobDigest,\n      Blob blob,\n      @Nullable String sourceRepository) {\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.blobDigest = blobDigest;\n    this.blob = blob;\n    this.sourceRepository = sourceRepository;\n  }\n\n  /**\n   * Returns a {@link RegistryEndpointProvider} for initializing the BLOB upload with an existence\n   * check.\n   */\n  RegistryEndpointProvider<Optional<URL>> initializer() {\n    return new Initializer();\n  }\n\n  /**\n   * Returns a new Writer.\n   *\n   * @param location the upload URL\n   * @param writtenByteCountListener the listener for {@link Blob} push progress (written bytes)\n   * @return a {@link RegistryEndpointProvider} for writing the BLOB to an upload location\n   */\n  RegistryEndpointProvider<URL> writer(URL location, Consumer<Long> writtenByteCountListener) {\n    return new Writer(location, writtenByteCountListener);\n  }\n\n  /**\n   * Returns a new Committer.\n   *\n   * @param location the upload URL\n   * @return a {@link RegistryEndpointProvider} for committing the written BLOB with its digest\n   */\n  RegistryEndpointProvider<Void> committer(URL location) {\n    return new Committer(location);\n  }\n\n  private RegistryErrorException buildRegistryErrorException(String reason) {\n    RegistryErrorExceptionBuilder registryErrorExceptionBuilder =\n        new RegistryErrorExceptionBuilder(getActionDescription());\n    registryErrorExceptionBuilder.addReason(reason);\n    return registryErrorExceptionBuilder.build();\n  }\n\n  /**\n   * Returns the common action description for {@link Initializer}, {@link Writer}, and {@link\n   * Committer}.\n   */\n  private String getActionDescription() {\n    return \"push BLOB for \"\n        + registryEndpointRequestProperties.getServerUrl()\n        + \"/\"\n        + registryEndpointRequestProperties.getImageName()\n        + \" with digest \"\n        + blobDigest;\n  }\n\n  /**\n   * Extract the {@code Location} header from the response to get the new location for the next\n   * request.\n   *\n   * <p>The {@code Location} header can be relative or absolute.\n   *\n   * @see <a\n   *     href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location#Directives\">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location#Directives</a>\n   * @param response the response to extract the 'Location' header from\n   * @return the new location for the next request\n   * @throws RegistryErrorException if there was not a single 'Location' header\n   */\n  private URL getRedirectLocation(Response response) throws RegistryErrorException {\n    // Extracts and returns the 'Location' header.\n    List<String> locationHeaders = response.getHeader(\"Location\");\n    if (locationHeaders.size() != 1) {\n      throw buildRegistryErrorException(\n          \"Expected 1 'Location' header, but found \" + locationHeaders.size());\n    }\n\n    String locationHeader = locationHeaders.get(0);\n    return response.getRequestUrl().toURL(locationHeader);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/ErrorCodes.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\n/**\n * Enumerated errors that can be received from the Registry API.\n *\n * <p>Descriptions are from:\n *\n * @see <a\n *     href=\"https://docs.docker.com/registry/spec/api/#errors-2\">https://docs.docker.com/registry/spec/api/#errors-2</a>\n */\nenum ErrorCodes {\n\n  /**\n   * This error may be returned when a blob is unknown to the registry in a specified repository.\n   * This can be returned with a standard get or if a manifest references an unknown layer during\n   * upload.\n   */\n  BLOB_UNKNOWN,\n\n  /** The blob upload encountered an error and can no longer proceed. */\n  BLOB_UPLOAD_INVALID,\n\n  /** If a blob upload has been cancelled or was never started, this error code may be returned. */\n  BLOB_UPLOAD_UNKNOWN,\n\n  /**\n   * When a blob is uploaded, the registry will check that the content matches the digest provided\n   * by the client. The error may include a detail structure with the key \"digest\", including the\n   * invalid digest string. This error may also be returned when a manifest includes an invalid\n   * layer digest.\n   */\n  DIGEST_INVALID,\n\n  /** This error may be returned when a manifest blob is unknown to the registry. */\n  MANIFEST_BLOB_UNKNOWN,\n\n  /**\n   * During upload, manifests undergo several checks ensuring validity. If those checks fail, this\n   * error may be returned, unless a more specific error is included. The detail will contain\n   * information the failed validation.\n   */\n  MANIFEST_INVALID,\n\n  /**\n   * This error is returned when the manifest, identified by name and tag is unknown to the\n   * repository.\n   */\n  MANIFEST_UNKNOWN,\n\n  /**\n   * During manifest upload, if the manifest fails signature verification, this error will be\n   * returned.\n   */\n  MANIFEST_UNVERIFIED,\n\n  /** Invalid repository name encountered either during manifest validation or any API operation. */\n  NAME_INVALID,\n\n  /** This is returned if the name used during an operation is unknown to the registry. */\n  NAME_UNKNOWN,\n\n  /**\n   * When a layer is uploaded, the provided size will be checked against the uploaded content. If\n   * they do not match, this error will be returned.\n   */\n  SIZE_INVALID,\n\n  /**\n   * During a manifest upload, if the tag in the manifest does not match the uri tag, this error\n   * will be returned.\n   */\n  TAG_INVALID,\n\n  /**\n   * The access controller was unable to authenticate the client. Often this will be accompanied by\n   * a Www-Authenticate HTTP response header indicating how to authenticate.\n   */\n  UNAUTHORIZED,\n\n  /** The access controller denied access for the operation on a resource. */\n  DENIED,\n\n  /** The operation was unsupported due to a missing implementation or invalid set of parameters. */\n  UNSUPPORTED\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/ErrorResponseUtil.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpResponseException;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;\nimport com.google.cloud.tools.jib.registry.json.ErrorResponseTemplate;\nimport java.io.IOException;\nimport java.util.List;\n\n/** Utility methods for parsing {@link ErrorResponseTemplate JSON-encoded error responses}. */\npublic class ErrorResponseUtil {\n\n  /**\n   * Extract an {@link ErrorCodes} response from the error object encoded in an {@link\n   * HttpResponseException}.\n   *\n   * @param responseException the response exception\n   * @return the parsed {@link ErrorCodes} if found\n   * @throws ResponseException rethrows the original exception if an error object could not be\n   *     parsed, if there were multiple error objects, or if the error code is unknown.\n   */\n  public static ErrorCodes getErrorCode(ResponseException responseException)\n      throws ResponseException {\n    // Obtain the error response code.\n    String errorContent = responseException.getContent();\n    if (errorContent == null) {\n      throw responseException;\n    }\n\n    try {\n      ErrorResponseTemplate errorResponse =\n          JsonTemplateMapper.readJson(errorContent, ErrorResponseTemplate.class);\n      List<ErrorEntryTemplate> errors = errorResponse.getErrors();\n      // There may be multiple error objects\n      if (errors.size() == 1) {\n        String errorCodeString = errors.get(0).getCode();\n        // May not get an error code back.\n        if (errorCodeString != null) {\n          // throws IllegalArgumentException if unknown error code\n          return ErrorCodes.valueOf(errorCodeString);\n        }\n      }\n\n    } catch (IOException | IllegalArgumentException ex) {\n      // Parse exception: either isn't an error object or unknown error code\n    }\n\n    // rethrow the original exception\n    throw responseException;\n  }\n\n  // not intended to be instantiated\n  private ErrorResponseUtil() {}\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestAndDigest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\n\n/** Stores a manifest and digest. */\npublic class ManifestAndDigest<T extends ManifestTemplate> {\n\n  private final T manifest;\n  private final DescriptorDigest digest;\n\n  public ManifestAndDigest(T manifest, DescriptorDigest digest) {\n    this.manifest = manifest;\n    this.digest = digest;\n  }\n\n  /**\n   * Gets the manifest.\n   *\n   * @return the manifest\n   */\n  public T getManifest() {\n    return manifest;\n  }\n\n  /**\n   * Gets the digest.\n   *\n   * @return the digest\n   */\n  public DescriptorDigest getDigest() {\n    return digest;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestChecker.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport java.util.Optional;\n\n/** Checks an image's manifest. */\nclass ManifestChecker<T extends ManifestTemplate>\n    extends AbstractManifestPuller<T, Optional<ManifestAndDigest<T>>> {\n\n  ManifestChecker(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      String imageQualifier,\n      Class<T> manifestTemplateClass) {\n    super(registryEndpointRequestProperties, imageQualifier, manifestTemplateClass);\n  }\n\n  @Override\n  public Optional<ManifestAndDigest<T>> handleHttpResponseException(\n      ResponseException responseException) throws ResponseException {\n    if (responseException.getStatusCode() != HttpStatusCodes.STATUS_CODE_NOT_FOUND) {\n      throw responseException;\n    }\n\n    if (responseException.getContent() == null) {\n      return Optional.empty();\n    }\n\n    // Find a MANIFEST_UNKNOWN error response code.\n    ErrorCodes errorCode = ErrorResponseUtil.getErrorCode(responseException);\n    if (errorCode == ErrorCodes.MANIFEST_UNKNOWN) {\n      return Optional.empty();\n    }\n\n    // MANIFEST_UNKNOWN was not found as a error response code.\n    throw responseException;\n  }\n\n  @Override\n  Optional<ManifestAndDigest<T>> computeReturn(ManifestAndDigest<T> manifestAndDigest) {\n    return Optional.of(manifestAndDigest);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestPuller.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\n\n/** Pulls an image's manifest. */\nclass ManifestPuller<T extends ManifestTemplate>\n    extends AbstractManifestPuller<T, ManifestAndDigest<T>> {\n\n  ManifestPuller(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      String imageTag,\n      Class<T> manifestTemplateClass) {\n    super(registryEndpointRequestProperties, imageTag, manifestTemplateClass);\n  }\n\n  @Override\n  ManifestAndDigest<T> computeReturn(ManifestAndDigest<T> manifestAndDigest) {\n    return manifestAndDigest;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestPusher.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpMethods;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.security.DigestException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.StringJoiner;\nimport org.apache.http.HttpStatus;\n\n/** Pushes an image's manifest. */\nclass ManifestPusher implements RegistryEndpointProvider<DescriptorDigest> {\n\n  /** Response header containing digest of pushed image. */\n  private static final String RESPONSE_DIGEST_HEADER = \"Docker-Content-Digest\";\n\n  /**\n   * Makes the warning for when the registry responds with an image digest that is not the expected\n   * digest of the image.\n   *\n   * @param expectedDigest the expected image digest\n   * @param receivedDigests the received image digests\n   * @return the warning message\n   */\n  private static String makeUnexpectedImageDigestWarning(\n      DescriptorDigest expectedDigest, List<String> receivedDigests) {\n    if (receivedDigests.isEmpty()) {\n      return \"Expected image digest \" + expectedDigest + \", but received none\";\n    }\n\n    StringJoiner message =\n        new StringJoiner(\", \", \"Expected image digest \" + expectedDigest + \", but received: \", \"\");\n    for (String receivedDigest : receivedDigests) {\n      message.add(receivedDigest);\n    }\n    return message.toString();\n  }\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  private final ManifestTemplate manifestTemplate;\n  private final String imageTag;\n  private final EventHandlers eventHandlers;\n\n  ManifestPusher(\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      ManifestTemplate manifestTemplate,\n      String imageTag,\n      EventHandlers eventHandlers) {\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.manifestTemplate = manifestTemplate;\n    this.imageTag = imageTag;\n    this.eventHandlers = eventHandlers;\n  }\n\n  @Override\n  public BlobHttpContent getContent() {\n    // TODO: Consider giving progress on manifest push as well?\n    return new BlobHttpContent(\n        Blobs.from(manifestTemplate), manifestTemplate.getManifestMediaType());\n  }\n\n  @Override\n  public List<String> getAccept() {\n    return Collections.emptyList();\n  }\n\n  @Override\n  public DescriptorDigest handleHttpResponseException(ResponseException responseException)\n      throws ResponseException, RegistryErrorException {\n    // docker registry 2.0 and 2.1 returns:\n    //   400 Bad Request\n    //   {\"errors\":[{\"code\":\"TAG_INVALID\",\"message\":\"manifest tag did not match URI\"}]}\n    // docker registry:2.2 returns:\n    //   400 Bad Request\n    //   {\"errors\":[{\"code\":\"MANIFEST_INVALID\",\"message\":\"manifest invalid\",\"detail\":{}}]}\n    // quay.io returns:\n    //   415 UNSUPPORTED MEDIA TYPE\n    //   {\"errors\":[{\"code\":\"MANIFEST_INVALID\",\"detail\":\n    //   {\"message\":\"manifest schema version not supported\"},\"message\":\"manifest invalid\"}]}\n\n    if (responseException.getStatusCode() != HttpStatus.SC_BAD_REQUEST\n        && responseException.getStatusCode() != HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE) {\n      throw responseException;\n    }\n\n    ErrorCodes errorCode = ErrorResponseUtil.getErrorCode(responseException);\n    if (errorCode == ErrorCodes.MANIFEST_INVALID || errorCode == ErrorCodes.TAG_INVALID) {\n      throw new RegistryErrorExceptionBuilder(getActionDescription(), responseException)\n          .addReason(\n              \"Registry may not support pushing OCI Manifest or \"\n                  + \"Docker Image Manifest Version 2, Schema 2\")\n          .build();\n    }\n    // rethrow: unhandled error response code.\n    throw responseException;\n  }\n\n  @Override\n  public DescriptorDigest handleResponse(Response response) throws IOException {\n    // Checks if the image digest is as expected.\n    DescriptorDigest expectedDigest = Digests.computeJsonDigest(manifestTemplate);\n\n    List<String> receivedDigests = response.getHeader(RESPONSE_DIGEST_HEADER);\n    if (receivedDigests.size() == 1) {\n      try {\n        DescriptorDigest receivedDigest = DescriptorDigest.fromDigest(receivedDigests.get(0));\n        if (expectedDigest.equals(receivedDigest)) {\n          return expectedDigest;\n        }\n\n      } catch (DigestException ex) {\n        // Invalid digest.\n      }\n    }\n\n    // The received digest is not as expected. Warns about this.\n    eventHandlers.dispatch(\n        LogEvent.warn(makeUnexpectedImageDigestWarning(expectedDigest, receivedDigests)));\n    return expectedDigest;\n  }\n\n  @Override\n  public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n    return new URL(\n        apiRouteBase + registryEndpointRequestProperties.getImageName() + \"/manifests/\" + imageTag);\n  }\n\n  @Override\n  public String getHttpMethod() {\n    return HttpMethods.PUT;\n  }\n\n  @Override\n  public String getActionDescription() {\n    return \"push image manifest for \"\n        + registryEndpointRequestProperties.getServerUrl()\n        + \"/\"\n        + registryEndpointRequestProperties.getImageName()\n        + \":\"\n        + imageTag;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAliasGroup.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/** Provides known aliases and alternative hosts for a given registry. */\npublic class RegistryAliasGroup {\n\n  private RegistryAliasGroup() {}\n\n  private static final ImmutableList<ImmutableSet<String>> REGISTRY_ALIAS_GROUPS =\n      ImmutableList.of(\n          // Docker Hub alias group (https://github.com/moby/moby/pull/28100)\n          ImmutableSet.of(\n              \"registry.hub.docker.com\", \"index.docker.io\", \"registry-1.docker.io\", \"docker.io\"));\n\n  /** Some registry names are symbolic. */\n  private static final ImmutableMap<String, String> REGISTRY_HOST_MAP =\n      ImmutableMap.of(\n          // https://github.com/docker/hub-feedback/issues/1767\n          \"docker.io\", \"registry-1.docker.io\");\n\n  /**\n   * Returns the list of registry aliases for the given {@code registry}, including {@code registry}\n   * as the first element.\n   *\n   * @param registry the registry for which the alias group is requested\n   * @return non-empty list of registries where {@code registry} is the first element\n   */\n  public static List<String> getAliasesGroup(String registry) {\n    for (ImmutableSet<String> aliasGroup : REGISTRY_ALIAS_GROUPS) {\n      if (aliasGroup.contains(registry)) {\n        // Found a group. Move the requested \"registry\" to the front before returning it.\n        Stream<String> self = Stream.of(registry);\n        Stream<String> withoutSelf = aliasGroup.stream().filter(alias -> !registry.equals(alias));\n        return Stream.concat(self, withoutSelf).collect(Collectors.toList());\n      }\n    }\n\n    return Collections.singletonList(registry);\n  }\n\n  /**\n   * Returns the server host name to use for the given registry.\n   *\n   * @param registry the name of the registry\n   * @return the registry host\n   */\n  public static String getHost(String registry) {\n    return REGISTRY_HOST_MAP.getOrDefault(registry, registry);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.api.client.http.HttpMethods;\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.http.Authorization;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Request;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.net.MediaType;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\n\n/**\n * Authenticates push/pull access with a registry service.\n *\n * @see <a\n *     href=\"https://docs.docker.com/registry/spec/auth/token/\">https://docs.docker.com/registry/spec/auth/token/</a>\n */\npublic class RegistryAuthenticator {\n\n  // TODO: Replace with a WWW-Authenticate header parser.\n  /**\n   * Instantiates from parsing a {@code WWW-Authenticate} header.\n   *\n   * @param authenticationMethod the {@code WWW-Authenticate} header value\n   * @param registryEndpointRequestProperties the registry request properties\n   * @param userAgent the {@code User-Agent} header value to use in later authentication calls\n   * @param httpClient HTTP client\n   * @return a new {@link RegistryAuthenticator} for authenticating with the registry service\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @see <a\n   *     href=\"https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate\">https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate</a>\n   */\n  static Optional<RegistryAuthenticator> fromAuthenticationMethod(\n      String authenticationMethod,\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      @Nullable String userAgent,\n      FailoverHttpClient httpClient)\n      throws RegistryAuthenticationFailedException {\n    // If the authentication method starts with 'basic' (case insensitive), no registry\n    // authentication is needed.\n    if (authenticationMethod.matches(\"^(?i)(basic).*\")) {\n      return Optional.empty();\n    }\n\n    String registryUrl = registryEndpointRequestProperties.getServerUrl();\n    String imageName = registryEndpointRequestProperties.getImageName();\n    // Checks that the authentication method starts with 'bearer ' (case insensitive).\n    if (!authenticationMethod.matches(\"^(?i)(bearer) .*\")) {\n      throw newRegistryAuthenticationFailedException(\n          registryUrl, imageName, authenticationMethod, \"Bearer\");\n    }\n\n    Pattern realmPattern = Pattern.compile(\"realm=\\\"(.*?)\\\"\");\n    Matcher realmMatcher = realmPattern.matcher(authenticationMethod);\n    if (!realmMatcher.find()) {\n      throw newRegistryAuthenticationFailedException(\n          registryUrl, imageName, authenticationMethod, \"realm\");\n    }\n    String realm = realmMatcher.group(1);\n\n    Pattern servicePattern = Pattern.compile(\"service=\\\"(.*?)\\\"\");\n    Matcher serviceMatcher = servicePattern.matcher(authenticationMethod);\n    // use the provided registry location when missing service (e.g., for OpenShift)\n    String service = serviceMatcher.find() ? serviceMatcher.group(1) : registryUrl;\n\n    return Optional.of(\n        new RegistryAuthenticator(\n            realm, service, registryEndpointRequestProperties, userAgent, httpClient));\n  }\n\n  private static RegistryAuthenticationFailedException newRegistryAuthenticationFailedException(\n      String registry, String repository, String authenticationMethod, String authParam) {\n    return new RegistryAuthenticationFailedException(\n        registry,\n        repository,\n        \"'\"\n            + authParam\n            + \"' was not found in the 'WWW-Authenticate' header, tried to parse: \"\n            + authenticationMethod);\n  }\n\n  /** Template for the authentication response JSON. */\n  @VisibleForTesting\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  static class AuthenticationResponseTemplate implements JsonTemplate {\n\n    @Nullable private String token;\n\n    /**\n     * {@code access_token} is accepted as an alias for {@code token}.\n     *\n     * @see <a\n     *     href=\"https://docs.docker.com/registry/spec/auth/token/#token-response-fields\">https://docs.docker.com/registry/spec/auth/token/#token-response-fields</a>\n     */\n    @Nullable\n    @JsonProperty(\"access_token\")\n    private String accessToken;\n\n    /** Returns {@link #token} if not null, or {@link #accessToken}. */\n    @Nullable\n    @VisibleForTesting\n    String getToken() {\n      if (token != null) {\n        return token;\n      }\n      return accessToken;\n    }\n  }\n\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  private final String realm;\n  private final String service;\n  @Nullable private final String userAgent;\n  private final FailoverHttpClient httpClient;\n\n  private RegistryAuthenticator(\n      String realm,\n      String service,\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      @Nullable String userAgent,\n      FailoverHttpClient httpClient) {\n    this.realm = realm;\n    this.service = service;\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.userAgent = userAgent;\n    this.httpClient = httpClient;\n  }\n\n  /**\n   * Authenticates permissions to pull.\n   *\n   * @param credential the credential used to authenticate\n   * @return an {@code Authorization} authenticating the pull\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @throws RegistryCredentialsNotSentException if authentication failed and credentials were not\n   *     sent over plain HTTP\n   */\n  public Authorization authenticatePull(@Nullable Credential credential)\n      throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {\n    return authenticate(credential, \"pull\");\n  }\n\n  /**\n   * Authenticates permission to pull and push.\n   *\n   * @param credential the credential used to authenticate\n   * @return an {@code Authorization} authenticating the push\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @throws RegistryCredentialsNotSentException if authentication failed and credentials were not\n   *     sent over plain HTTP\n   */\n  public Authorization authenticatePush(@Nullable Credential credential)\n      throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {\n    return authenticate(credential, \"pull,push\");\n  }\n\n  private String getServiceScopeRequestParameters(Map<String, String> repositoryScopes) {\n    StringBuilder parameters = new StringBuilder(\"service=\").append(service);\n    for (Map.Entry<String, String> pair : repositoryScopes.entrySet()) {\n      parameters\n          .append(\"&scope=repository:\")\n          .append(pair.getKey())\n          .append(\":\")\n          .append(pair.getValue());\n    }\n    return parameters.toString();\n  }\n\n  @VisibleForTesting\n  URL getAuthenticationUrl(@Nullable Credential credential, Map<String, String> repositoryScopes)\n      throws MalformedURLException {\n    return isOAuth2Auth(credential)\n        ? new URL(realm) // Required parameters will be sent via POST .\n        : new URL(realm + \"?\" + getServiceScopeRequestParameters(repositoryScopes));\n  }\n\n  @VisibleForTesting\n  String getAuthRequestParameters(\n      @Nullable Credential credential, Map<String, String> repositoryScopes) {\n    String serviceScope = getServiceScopeRequestParameters(repositoryScopes);\n    return isOAuth2Auth(credential)\n        ? serviceScope\n            // https://github.com/GoogleContainerTools/jib/pull/1545\n            + \"&client_id=jib.da031fe481a93ac107a95a96462358f9\"\n            + \"&grant_type=refresh_token&refresh_token=\"\n            // If OAuth2, credential.getPassword() is a refresh token.\n            + Verify.verifyNotNull(credential).getPassword()\n        : serviceScope;\n  }\n\n  @VisibleForTesting\n  boolean isOAuth2Auth(@Nullable Credential credential) {\n    return credential != null && credential.isOAuth2RefreshToken();\n  }\n\n  /**\n   * Sends the authentication request and retrieves the Bearer authorization token.\n   *\n   * @param credential the credential used to authenticate\n   * @param scope the scope of permissions to authenticate for\n   * @return the {@link Authorization} response\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @throws RegistryCredentialsNotSentException if authentication is failed and credentials were\n   *     not sent over plain HTTP\n   * @see <a\n   *     href=\"https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate\">https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate</a>\n   */\n  private Authorization authenticate(@Nullable Credential credential, String scope)\n      throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {\n    // try authorizing against both the main repository and the source repository too\n    // to enable cross-repository mounts on pushes\n    String sourceImageName = registryEndpointRequestProperties.getSourceImageName();\n    String imageName = registryEndpointRequestProperties.getImageName();\n    if (sourceImageName != null && !sourceImageName.equals(imageName)) {\n      try {\n        return authenticate(credential, ImmutableMap.of(imageName, scope, sourceImageName, \"pull\"));\n      } catch (RegistryAuthenticationFailedException ex) {\n        // Unable to obtain authorization with source image: fall through and try without\n      }\n    }\n    return authenticate(credential, ImmutableMap.of(imageName, scope));\n  }\n\n  private Authorization authenticate(\n      @Nullable Credential credential, Map<String, String> repositoryScopes)\n      throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {\n    String registryUrl = registryEndpointRequestProperties.getServerUrl();\n    String imageName = registryEndpointRequestProperties.getImageName();\n    try {\n      URL url = getAuthenticationUrl(credential, repositoryScopes);\n\n      Request.Builder requestBuilder =\n          Request.builder()\n              .setHttpTimeout(JibSystemProperties.getHttpTimeout())\n              .setUserAgent(userAgent);\n\n      if (isOAuth2Auth(credential)) {\n        String parameters = getAuthRequestParameters(credential, repositoryScopes);\n        requestBuilder.setBody(\n            new BlobHttpContent(Blobs.from(parameters), MediaType.FORM_DATA.toString()));\n      } else if (credential != null) {\n        requestBuilder.setAuthorization(\n            Authorization.fromBasicCredentials(credential.getUsername(), credential.getPassword()));\n      }\n\n      String httpMethod = isOAuth2Auth(credential) ? HttpMethods.POST : HttpMethods.GET;\n      try (Response response = httpClient.call(httpMethod, url, requestBuilder.build())) {\n\n        AuthenticationResponseTemplate responseJson =\n            JsonTemplateMapper.readJson(response.getBody(), AuthenticationResponseTemplate.class);\n\n        if (responseJson.getToken() == null) {\n          throw new RegistryAuthenticationFailedException(\n              registryUrl,\n              imageName,\n              \"Did not get token in authentication response from \"\n                  + getAuthenticationUrl(credential, repositoryScopes)\n                  + \"; parameters: \"\n                  + getAuthRequestParameters(credential, repositoryScopes));\n        }\n        return Authorization.fromBearerToken(responseJson.getToken());\n      }\n\n    } catch (ResponseException ex) {\n      if (ex.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED\n          && ex.requestAuthorizationCleared()) {\n        throw new RegistryCredentialsNotSentException(registryUrl, imageName);\n      }\n      throw new RegistryAuthenticationFailedException(registryUrl, imageName, ex);\n\n    } catch (IOException ex) {\n      throw new RegistryAuthenticationFailedException(registryUrl, imageName, ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.builder.TimerEventDispatcher;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.http.Authorization;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableSetMultimap;\nimport com.google.common.collect.Multimap;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.ThreadSafe;\n\n/** Interfaces with a registry. Thread-safe. */\n@ThreadSafe\npublic class RegistryClient {\n\n  /** Factory for creating {@link RegistryClient}s. */\n  public static class Factory {\n\n    private final EventHandlers eventHandlers;\n    private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n    private final FailoverHttpClient httpClient;\n\n    @Nullable private String userAgent;\n    @Nullable private Credential credential;\n\n    private Factory(\n        EventHandlers eventHandlers,\n        RegistryEndpointRequestProperties registryEndpointRequestProperties,\n        FailoverHttpClient httpClient) {\n      this.eventHandlers = eventHandlers;\n      this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n      this.httpClient = httpClient;\n    }\n\n    /**\n     * Sets the authentication credentials to use to authenticate with the registry.\n     *\n     * @param credential the {@link Credential} to access the registry/repository\n     * @return this\n     */\n    public Factory setCredential(@Nullable Credential credential) {\n      this.credential = credential;\n      return this;\n    }\n\n    /**\n     * Sets the value of {@code User-Agent} in headers for registry requests.\n     *\n     * @param userAgent user agent string\n     * @return this\n     */\n    public Factory setUserAgent(@Nullable String userAgent) {\n      this.userAgent = userAgent;\n      return this;\n    }\n\n    /**\n     * Creates a new {@link RegistryClient}.\n     *\n     * @return the new {@link RegistryClient}\n     */\n    public RegistryClient newRegistryClient() {\n      return new RegistryClient(\n          eventHandlers, credential, registryEndpointRequestProperties, userAgent, httpClient);\n    }\n  }\n\n  private static final int MAX_BEARER_TOKEN_REFRESH_TRIES = 5;\n\n  /**\n   * Creates a new {@link Factory} for building a {@link RegistryClient}.\n   *\n   * @param eventHandlers the event handlers used for dispatching log events\n   * @param serverUrl the server URL for the registry (for example, {@code gcr.io})\n   * @param imageName the image/repository name (also known as, namespace)\n   * @param httpClient HTTP client\n   * @return the new {@link Factory}\n   */\n  public static Factory factory(\n      EventHandlers eventHandlers,\n      String serverUrl,\n      String imageName,\n      FailoverHttpClient httpClient) {\n    return new Factory(\n        eventHandlers, new RegistryEndpointRequestProperties(serverUrl, imageName), httpClient);\n  }\n\n  /**\n   * Creates a new {@link Factory} for building a {@link RegistryClient}.\n   *\n   * @param eventHandlers the event handlers used for dispatching log events\n   * @param serverUrl the server URL for the registry (for example, {@code gcr.io})\n   * @param imageName the image/repository name (also known as, namespace)\n   * @param sourceImageName additional source image to request pull permission from the registry\n   * @param httpClient HTTP client\n   * @return the new {@link Factory}\n   */\n  public static Factory factory(\n      EventHandlers eventHandlers,\n      String serverUrl,\n      String imageName,\n      String sourceImageName,\n      FailoverHttpClient httpClient) {\n    return new Factory(\n        eventHandlers,\n        new RegistryEndpointRequestProperties(serverUrl, imageName, sourceImageName),\n        httpClient);\n  }\n\n  /**\n   * A simple class representing the payload of a <a\n   * href=\"https://docs.docker.com/registry/spec/auth/jwt/\">Docker Registry v2 Bearer Token</a>\n   * which lists the set of access claims granted.\n   *\n   * <pre>\n   * {\"access\":[{\"type\": \"repository\",\"name\": \"library/openjdk\",\"actions\":[\"push\",\"pull\"]}]}\n   * </pre>\n   *\n   * @see AccessClaim\n   */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  private static class TokenPayloadTemplate implements JsonTemplate {\n\n    @Nullable private List<AccessClaim> access;\n  }\n\n  /**\n   * Represents an access claim for a repository in a Docker Registry Bearer Token payload.\n   *\n   * <pre>{\"type\": \"repository\",\"name\": \"library/openjdk\",\"actions\":[\"push\",\"pull\"]}</pre>\n   */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  private static class AccessClaim implements JsonTemplate {\n\n    @Nullable private String type;\n    @Nullable private String name;\n    @Nullable private List<String> actions;\n  }\n\n  /**\n   * Decode the <a href=\"https://docs.docker.com/registry/spec/auth/jwt/\">Docker Registry v2 Bearer\n   * Token</a> to list the granted repositories with their levels of access.\n   *\n   * @param token a Docker Registry Bearer Token\n   * @return a mapping of repository to granted access scopes, or {@code null} if the token is not a\n   *     Docker Registry Bearer Token\n   */\n  @VisibleForTesting\n  @Nullable\n  static Multimap<String, String> decodeTokenRepositoryGrants(String token) {\n    // Docker Registry Bearer Tokens are based on JWT.  A valid JWT is a set of 3 base64-encoded\n    // parts (header, payload, signature), collated with a \".\".  The header and payload are\n    // JSON objects.\n    String[] jwtParts = token.split(\"\\\\.\", -1);\n    if (jwtParts.length != 3) {\n      return null;\n    }\n    byte[] payloadData = Base64.getDecoder().decode(jwtParts[1]);\n\n    // The payload looks like:\n    // {\n    //   \"access\":[{\"type\":\"repository\",\"name\":\"repository/name\",\"actions\":[\"pull\"]}],\n    //   \"aud\":\"registry.docker.io\",\n    //   \"iss\":\"auth.docker.io\",\n    //   \"exp\":999,\n    //   \"iat\":999,\n    //   \"jti\":\"zzzz\",\n    //   \"nbf\":999,\n    //   \"sub\":\"e3ae001d-xxx\"\n    // }\n    //\n    try {\n      TokenPayloadTemplate payload =\n          JsonTemplateMapper.readJson(payloadData, TokenPayloadTemplate.class);\n      if (payload.access == null) {\n        return null;\n      }\n      return payload.access.stream()\n          .filter(claim -> \"repository\".equals(claim.type))\n          .collect(\n              ImmutableSetMultimap.<AccessClaim, String, String>flatteningToImmutableSetMultimap(\n                  claim -> claim.name,\n                  claim -> claim.actions == null ? Stream.empty() : claim.actions.stream()));\n    } catch (IOException ex) {\n      return null;\n    }\n  }\n\n  private final EventHandlers eventHandlers;\n  @Nullable private final Credential credential;\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  @Nullable private final String userAgent;\n  private final FailoverHttpClient httpClient;\n\n  // mutable\n  private final AtomicReference<Authorization> authorization = new AtomicReference<>();\n  private boolean readOnlyBearerAuth;\n  private final AtomicReference<RegistryAuthenticator> initialBearerAuthenticator =\n      new AtomicReference<>();\n\n  /**\n   * Instantiate with {@link #factory}.\n   *\n   * @param eventHandlers the event handlers used for dispatching log events\n   * @param credential credential for registry/repository; will not be used unless {@link\n   *     #configureBasicAuth} or {@link #doBearerAuth} is called\n   * @param registryEndpointRequestProperties properties of registry endpoint requests\n   * @param userAgent {@code User-Agent} header to send with the request, can be {@code null}\n   * @param httpClient HTTP client\n   */\n  private RegistryClient(\n      EventHandlers eventHandlers,\n      @Nullable Credential credential,\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      @Nullable String userAgent,\n      FailoverHttpClient httpClient) {\n    this.eventHandlers = eventHandlers;\n    this.credential = credential;\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.userAgent = userAgent;\n    this.httpClient = httpClient;\n  }\n\n  /** Configure basic authentication on this registry client. */\n  public void configureBasicAuth() {\n    Preconditions.checkNotNull(credential);\n    Preconditions.checkState(!credential.isOAuth2RefreshToken());\n\n    authorization.set(\n        Authorization.fromBasicCredentials(credential.getUsername(), credential.getPassword()));\n\n    String registry = registryEndpointRequestProperties.getServerUrl();\n    String repository = registryEndpointRequestProperties.getImageName();\n    eventHandlers.dispatch(\n        LogEvent.debug(\"configured basic auth for \" + registry + \"/\" + repository));\n  }\n\n  /**\n   * Attempts bearer authentication for pull.\n   *\n   * @return {@code true} if bearer authentication succeeded; {@code false} if the server expects\n   *     basic authentication (and thus bearer authentication was not attempted)\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @throws RegistryCredentialsNotSentException if authentication failed and credentials were not\n   *     sent over plain HTTP\n   */\n  public boolean doPullBearerAuth() throws IOException, RegistryException {\n    return doBearerAuth(true);\n  }\n\n  /**\n   * Attempts bearer authentication for pull and push.\n   *\n   * @return true if bearer authentication succeeded; false if the server expects basic\n   *     authentication (and thus bearer authentication was not attempted)\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @throws RegistryCredentialsNotSentException if authentication failed and credentials were not\n   *     sent over plain HTTP\n   */\n  public boolean doPushBearerAuth() throws IOException, RegistryException {\n    return doBearerAuth(false);\n  }\n\n  private boolean doBearerAuth(boolean readOnlyBearerAuth) throws IOException, RegistryException {\n    String registry = registryEndpointRequestProperties.getServerUrl();\n    String repository = registryEndpointRequestProperties.getImageName();\n    String image = registry + \"/\" + repository;\n    eventHandlers.dispatch(LogEvent.debug(\"attempting bearer auth for \" + image + \"...\"));\n\n    Optional<RegistryAuthenticator> authenticator =\n        callRegistryEndpoint(\n            new AuthenticationMethodRetriever(\n                registryEndpointRequestProperties, getUserAgent(), httpClient));\n    if (!authenticator.isPresent()) {\n      eventHandlers.dispatch(LogEvent.debug(\"server requires basic auth for \" + image));\n      return false; // server returned \"WWW-Authenticate: Basic ...\"\n    }\n\n    doBearerAuth(readOnlyBearerAuth, authenticator.get());\n    return true;\n  }\n\n  private void doBearerAuth(boolean readOnlyBearerAuth, RegistryAuthenticator authenticator)\n      throws RegistryException {\n    initialBearerAuthenticator.set(authenticator);\n    if (readOnlyBearerAuth) {\n      authorization.set(authenticator.authenticatePull(credential));\n    } else {\n      authorization.set(authenticator.authenticatePush(credential));\n    }\n    this.readOnlyBearerAuth = readOnlyBearerAuth;\n\n    eventHandlers.dispatch(\n        LogEvent.debug(\n            \"bearer auth succeeded for \"\n                + registryEndpointRequestProperties.getServerUrl()\n                + \"/\"\n                + registryEndpointRequestProperties.getImageName()));\n  }\n\n  private Authorization refreshBearerAuth(@Nullable String wwwAuthenticate)\n      throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {\n    Preconditions.checkState(isBearerAuth(authorization.get()));\n\n    String registry = registryEndpointRequestProperties.getServerUrl();\n    String repository = registryEndpointRequestProperties.getImageName();\n    eventHandlers.dispatch(\n        LogEvent.debug(\"refreshing bearer auth token for \" + registry + \"/\" + repository + \"...\"));\n\n    if (wwwAuthenticate != null) {\n      Optional<RegistryAuthenticator> authenticator =\n          RegistryAuthenticator.fromAuthenticationMethod(\n              wwwAuthenticate, registryEndpointRequestProperties, getUserAgent(), httpClient);\n      if (authenticator.isPresent()) {\n        if (readOnlyBearerAuth) {\n          return authenticator.get().authenticatePull(credential);\n        }\n        return authenticator.get().authenticatePush(credential);\n      }\n    }\n\n    eventHandlers.dispatch(\n        LogEvent.debug(\n            \"server did not return 'WWW-Authenticate: Bearer' header. Actual: \" + wwwAuthenticate));\n    if (readOnlyBearerAuth) {\n      return Verify.verifyNotNull(initialBearerAuthenticator.get()).authenticatePull(credential);\n    }\n    return Verify.verifyNotNull(initialBearerAuthenticator.get()).authenticatePush(credential);\n  }\n\n  /**\n   * Configure basic authentication or attempts bearer authentication for pulling based on the\n   * specified authentication method in a server response.\n   *\n   * @param wwwAuthenticate {@code WWW-Authenticate} HTTP header value from a server response\n   *     specifying a required authentication method\n   * @throws RegistryException if communicating with the endpoint fails\n   * @throws RegistryAuthenticationFailedException if authentication fails\n   * @throws RegistryCredentialsNotSentException if authentication failed and credentials were not\n   */\n  public void authPullByWwwAuthenticate(String wwwAuthenticate) throws RegistryException {\n    Optional<RegistryAuthenticator> authenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n            wwwAuthenticate, registryEndpointRequestProperties, getUserAgent(), httpClient);\n    if (authenticator.isPresent()) {\n      doBearerAuth(true, authenticator.get());\n    } else if (credential != null && !credential.isOAuth2RefreshToken()) {\n      configureBasicAuth();\n    }\n  }\n\n  /**\n   * Check if a manifest referred to by {@code imageQualifier} (tag or digest) exists on the\n   * registry.\n   *\n   * @param imageQualifier the tag or digest to check for\n   * @return the {@link ManifestAndDigest} referred to by {@code imageQualifier} if the manifest\n   *     exists on the registry, or {@link Optional#empty()} otherwise\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   */\n  public Optional<ManifestAndDigest<ManifestTemplate>> checkManifest(String imageQualifier)\n      throws IOException, RegistryException {\n    ManifestChecker<ManifestTemplate> manifestChecker =\n        new ManifestChecker<>(\n            registryEndpointRequestProperties, imageQualifier, ManifestTemplate.class);\n    return callRegistryEndpoint(manifestChecker);\n  }\n\n  /**\n   * Pulls the image manifest and digest for a specific tag.\n   *\n   * @param <T> child type of ManifestTemplate\n   * @param imageQualifier the tag or digest to pull on\n   * @param manifestTemplateClass the specific version of manifest template to pull, or {@link\n   *     ManifestTemplate} to pull predefined subclasses; see: {@link\n   *     ManifestPuller#handleResponse(Response)}\n   * @return the {@link ManifestAndDigest}\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   */\n  public <T extends ManifestTemplate> ManifestAndDigest<T> pullManifest(\n      String imageQualifier, Class<T> manifestTemplateClass) throws IOException, RegistryException {\n    ManifestPuller<T> manifestPuller =\n        new ManifestPuller<>(\n            registryEndpointRequestProperties, imageQualifier, manifestTemplateClass);\n    return callRegistryEndpoint(manifestPuller);\n  }\n\n  public ManifestAndDigest<ManifestTemplate> pullManifest(String imageQualifier)\n      throws IOException, RegistryException {\n    return pullManifest(imageQualifier, ManifestTemplate.class);\n  }\n\n  /**\n   * Pushes the image manifest for a specific tag.\n   *\n   * @param manifestTemplate the image manifest\n   * @param imageTag the tag to push on\n   * @return the digest of the pushed image\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   */\n  public DescriptorDigest pushManifest(ManifestTemplate manifestTemplate, String imageTag)\n      throws IOException, RegistryException {\n    if (isBearerAuth(authorization.get()) && readOnlyBearerAuth) {\n      throw new IllegalStateException(\"push may fail with pull-only bearer auth token\");\n    }\n\n    return callRegistryEndpoint(\n        new ManifestPusher(\n            registryEndpointRequestProperties, manifestTemplate, imageTag, eventHandlers));\n  }\n\n  /**\n   * Check if a blob is on the registry.\n   *\n   * @param blobDigest the blob digest to check for\n   * @return the BLOB's {@link BlobDescriptor} if the BLOB exists on the registry, or {@link\n   *     Optional#empty()} if it doesn't\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   */\n  public Optional<BlobDescriptor> checkBlob(DescriptorDigest blobDigest)\n      throws IOException, RegistryException {\n    BlobChecker blobChecker = new BlobChecker(registryEndpointRequestProperties, blobDigest);\n    return callRegistryEndpoint(blobChecker);\n  }\n\n  /**\n   * Gets the BLOB referenced by {@code blobDigest}. Note that the BLOB is only pulled when it is\n   * written out.\n   *\n   * @param blobDigest the digest of the BLOB to download\n   * @param blobSizeListener callback to receive the total size of the BLOb to pull\n   * @param writtenByteCountListener listens on byte count written to an output stream during the\n   *     pull\n   * @return a {@link Blob}\n   */\n  public Blob pullBlob(\n      DescriptorDigest blobDigest,\n      Consumer<Long> blobSizeListener,\n      Consumer<Long> writtenByteCountListener) {\n    return Blobs.from(\n        outputStream -> {\n          try {\n            callRegistryEndpoint(\n                new BlobPuller(\n                    registryEndpointRequestProperties,\n                    blobDigest,\n                    outputStream,\n                    blobSizeListener,\n                    writtenByteCountListener));\n\n          } catch (RegistryException ex) {\n            throw new IOException(ex);\n          }\n        },\n        false);\n  }\n\n  /**\n   * Pushes the BLOB. If the {@code sourceRepository} is provided then the remote registry may skip\n   * if the BLOB already exists on the registry.\n   *\n   * @param blobDigest the digest of the BLOB, used for existence-check\n   * @param blob the BLOB to push\n   * @param sourceRepository if pushing to the same registry then the source image, or {@code null}\n   *     otherwise; used to optimize the BLOB push\n   * @param writtenByteCountListener listens on byte count written to the registry during the push\n   * @return {@code true} if the BLOB already exists on the registry and pushing was skipped; false\n   *     if the BLOB was pushed\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   */\n  public boolean pushBlob(\n      DescriptorDigest blobDigest,\n      Blob blob,\n      @Nullable String sourceRepository,\n      Consumer<Long> writtenByteCountListener)\n      throws IOException, RegistryException {\n    if (isBearerAuth(authorization.get()) && readOnlyBearerAuth) {\n      throw new IllegalStateException(\"push may fail with pull-only bearer auth token\");\n    }\n\n    if (sourceRepository != null\n        && !(JibSystemProperties.useCrossRepositoryBlobMounts()\n            && canAttemptBlobMount(authorization.get(), sourceRepository))) {\n      // don't bother requesting a cross-repository blob-mount if we don't have access\n      sourceRepository = null;\n    }\n    BlobPusher blobPusher =\n        new BlobPusher(registryEndpointRequestProperties, blobDigest, blob, sourceRepository);\n\n    try (TimerEventDispatcher timerEventDispatcher =\n        new TimerEventDispatcher(eventHandlers, \"pushBlob\")) {\n      try (TimerEventDispatcher timerEventDispatcher2 =\n          timerEventDispatcher.subTimer(\"pushBlob POST \" + blobDigest)) {\n\n        // POST /v2/<name>/blobs/uploads/?mount={blob.digest}&from={sourceRepository}\n        // POST /v2/<name>/blobs/uploads/\n        Optional<URL> patchLocation = callRegistryEndpoint(blobPusher.initializer());\n        if (!patchLocation.isPresent()) {\n          // The BLOB exists already.\n          return true;\n        }\n\n        timerEventDispatcher2.lap(\"pushBlob PATCH \" + blobDigest);\n\n        // PATCH <Location> with BLOB\n        URL putLocation =\n            callRegistryEndpoint(blobPusher.writer(patchLocation.get(), writtenByteCountListener));\n\n        timerEventDispatcher2.lap(\"pushBlob PUT \" + blobDigest);\n\n        // PUT <Location>?digest={blob.digest}\n        callRegistryEndpoint(blobPusher.committer(putLocation));\n\n        return false;\n      }\n    }\n  }\n\n  /**\n   * Check if the authorization allows using the specified repository can be mounted by the remote\n   * registry as a source for blobs. More specifically, we can only check if the repository is not\n   * disallowed.\n   *\n   * @param repository repository in question\n   * @return {@code true} if the repository appears to be mountable\n   */\n  @VisibleForTesting\n  static boolean canAttemptBlobMount(@Nullable Authorization authorization, String repository) {\n    if (!isBearerAuth(authorization)) {\n      // Authorization methods other than the Docker Container Registry Token don't provide\n      // information as to which repositories are accessible.  The caller should attempt the mount\n      // and rely on the registry fallback as required by the spec.\n      // https://docs.docker.com/registry/spec/api/#pushing-an-image\n      return true;\n    }\n    // if null then does not appear to be a DCRT\n    Multimap<String, String> repositoryGrants =\n        decodeTokenRepositoryGrants(Verify.verifyNotNull(authorization).getToken());\n    return repositoryGrants == null || repositoryGrants.containsEntry(repository, \"pull\");\n  }\n\n  private static boolean isBearerAuth(@Nullable Authorization authorization) {\n    return authorization != null && \"bearer\".equalsIgnoreCase(authorization.getScheme());\n  }\n\n  @Nullable\n  @VisibleForTesting\n  String getUserAgent() {\n    return userAgent;\n  }\n\n  /**\n   * Calls the registry endpoint.\n   *\n   * @param registryEndpointProvider the {@link RegistryEndpointProvider} to the endpoint\n   * @throws IOException if communicating with the endpoint fails\n   * @throws RegistryException if communicating with the endpoint fails\n   */\n  private <T> T callRegistryEndpoint(RegistryEndpointProvider<T> registryEndpointProvider)\n      throws IOException, RegistryException {\n    int bearerTokenRefreshes = 0;\n    while (true) {\n      try {\n        return new RegistryEndpointCaller<>(\n                eventHandlers,\n                getUserAgent(),\n                registryEndpointProvider,\n                authorization.get(),\n                registryEndpointRequestProperties,\n                httpClient)\n            .call();\n\n      } catch (RegistryUnauthorizedException ex) {\n        if (ex.getHttpResponseException().getStatusCode()\n                != HttpStatusCodes.STATUS_CODE_UNAUTHORIZED\n            || !isBearerAuth(authorization.get())\n            || ++bearerTokenRefreshes >= MAX_BEARER_TOKEN_REFRESH_TRIES) {\n          throw ex;\n        }\n\n        // Because we successfully did bearer authentication initially, getting 401 here probably\n        // means the token was expired.\n        String wwwAuthenticate = ex.getHttpResponseException().getHeaders().getAuthenticate();\n        authorization.set(refreshBearerAuth(wwwAuthenticate));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryCredentialsNotSentException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\n\n/** Thrown when registry request was unauthorized because credentials weren't sent. */\npublic class RegistryCredentialsNotSentException extends RegistryException {\n\n  /**\n   * Identifies the image registry and repository that denied access.\n   *\n   * @param registry the image registry\n   * @param repository the image repository\n   */\n  RegistryCredentialsNotSentException(String registry, String repository) {\n    super(\n        \"Required credentials for \"\n            + registry\n            + \"/\"\n            + repository\n            + \" were not sent because the connection was over HTTP\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointCaller.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.InsecureRegistryException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.http.Authorization;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Request;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;\nimport com.google.cloud.tools.jib.registry.json.ErrorResponseTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Locale;\nimport javax.annotation.Nullable;\nimport javax.net.ssl.SSLException;\n\n/**\n * Makes requests to a registry endpoint.\n *\n * @param <T> the type returned by calling the endpoint\n */\nclass RegistryEndpointCaller<T> {\n\n  /**\n   * <a href =\n   * \"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308\">https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308</a>.\n   */\n  @VisibleForTesting static final int STATUS_CODE_PERMANENT_REDIRECT = 308;\n\n  // https://github.com/GoogleContainerTools/jib/issues/1316\n  @VisibleForTesting\n  static boolean isBrokenPipe(IOException original) {\n    Throwable exception = original;\n    while (exception != null) {\n      String message = exception.getMessage();\n      if (message != null && message.toLowerCase(Locale.US).contains(\"broken pipe\")) {\n        return true;\n      }\n\n      exception = exception.getCause();\n      if (exception == original) { // just in case if there's a circular chain\n        return false;\n      }\n    }\n    return false;\n  }\n\n  private final EventHandlers eventHandlers;\n  @Nullable private final String userAgent;\n  private final RegistryEndpointProvider<T> registryEndpointProvider;\n  @Nullable private final Authorization authorization;\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties;\n  private final FailoverHttpClient httpClient;\n\n  /**\n   * Constructs with parameters for making the request.\n   *\n   * @param eventHandlers the event dispatcher used for dispatching log events\n   * @param userAgent {@code User-Agent} header to send with the request\n   * @param registryEndpointProvider the {@link RegistryEndpointProvider} to the endpoint\n   * @param authorization optional authentication credentials to use\n   * @param registryEndpointRequestProperties properties of the registry endpoint request\n   * @param httpClient HTTP client\n   */\n  RegistryEndpointCaller(\n      EventHandlers eventHandlers,\n      @Nullable String userAgent,\n      RegistryEndpointProvider<T> registryEndpointProvider,\n      @Nullable Authorization authorization,\n      RegistryEndpointRequestProperties registryEndpointRequestProperties,\n      FailoverHttpClient httpClient) {\n    this.eventHandlers = eventHandlers;\n    this.userAgent = userAgent;\n    this.registryEndpointProvider = registryEndpointProvider;\n    this.authorization = authorization;\n    this.registryEndpointRequestProperties = registryEndpointRequestProperties;\n    this.httpClient = httpClient;\n  }\n\n  /**\n   * Makes the request to the endpoint.\n   *\n   * @return an object representing the response, or {@code null}\n   * @throws IOException for most I/O exceptions when making the request\n   * @throws RegistryException for known exceptions when interacting with the registry\n   */\n  T call() throws IOException, RegistryException {\n    String apiRouteBase = \"https://\" + registryEndpointRequestProperties.getServerUrl() + \"/v2/\";\n    URL initialRequestUrl = registryEndpointProvider.getApiRoute(apiRouteBase);\n    return call(initialRequestUrl);\n  }\n\n  /**\n   * Calls the registry endpoint with a certain {@link URL}.\n   *\n   * @param url the endpoint URL to call\n   * @return an object representing the response\n   * @throws IOException for most I/O exceptions when making the request\n   * @throws RegistryException for known exceptions when interacting with the registry\n   */\n  private T call(URL url) throws IOException, RegistryException {\n    String serverUrl = registryEndpointRequestProperties.getServerUrl();\n    String imageName = registryEndpointRequestProperties.getImageName();\n\n    Request.Builder requestBuilder =\n        Request.builder()\n            .setUserAgent(userAgent)\n            .setHttpTimeout(JibSystemProperties.getHttpTimeout())\n            .setAccept(registryEndpointProvider.getAccept())\n            .setBody(registryEndpointProvider.getContent())\n            .setAuthorization(authorization);\n\n    try (Response response =\n        httpClient.call(registryEndpointProvider.getHttpMethod(), url, requestBuilder.build())) {\n\n      return registryEndpointProvider.handleResponse(response);\n\n    } catch (ResponseException ex) {\n      // First, see if the endpoint provider handles an exception as an expected response.\n      try {\n        return registryEndpointProvider.handleHttpResponseException(ex);\n\n      } catch (ResponseException responseException) {\n        if (responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_BAD_REQUEST\n            || responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND\n            || responseException.getStatusCode()\n                == HttpStatusCodes.STATUS_CODE_METHOD_NOT_ALLOWED) {\n          // The name or reference was invalid.\n          throw newRegistryErrorException(responseException);\n\n        } else if (responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_FORBIDDEN) {\n          throw new RegistryUnauthorizedException(serverUrl, imageName, responseException);\n\n        } else if (responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) {\n          if (responseException.requestAuthorizationCleared()) {\n            throw new RegistryCredentialsNotSentException(serverUrl, imageName);\n          } else {\n            // Credentials are either missing or wrong.\n            throw new RegistryUnauthorizedException(serverUrl, imageName, responseException);\n          }\n\n        } else {\n          // Unknown\n          throw responseException;\n        }\n      }\n\n    } catch (IOException ex) {\n      logError(\"I/O error for image [\" + serverUrl + \"/\" + imageName + \"]:\");\n      logError(\"    \" + ex.getClass().getName());\n      logError(\"    \" + (ex.getMessage() == null ? \"(null exception message)\" : ex.getMessage()));\n      logErrorIfBrokenPipe(ex);\n\n      if (ex instanceof SSLException) {\n        throw new InsecureRegistryException(url, ex);\n      }\n      throw ex;\n    }\n  }\n\n  @VisibleForTesting\n  RegistryErrorException newRegistryErrorException(ResponseException responseException) {\n    RegistryErrorExceptionBuilder registryErrorExceptionBuilder =\n        new RegistryErrorExceptionBuilder(\n            registryEndpointProvider.getActionDescription(), responseException);\n    if (responseException.getContent() != null) {\n      try {\n        ErrorResponseTemplate errorResponse =\n            JsonTemplateMapper.readJson(\n                responseException.getContent(), ErrorResponseTemplate.class);\n        for (ErrorEntryTemplate errorEntry : errorResponse.getErrors()) {\n          registryErrorExceptionBuilder.addReason(errorEntry);\n        }\n      } catch (IOException ex) {\n        registryErrorExceptionBuilder.addReason(\n            \"registry returned error code \"\n                + responseException.getStatusCode()\n                + \"; possible causes include invalid or wrong reference. Actual error output follows:\\n\"\n                + responseException.getContent()\n                + \"\\n\");\n      }\n    } else {\n      registryErrorExceptionBuilder.addReason(\n          \"registry returned error code \"\n              + responseException.getStatusCode()\n              + \" but did not return any details; possible causes include invalid or wrong reference, or proxy/firewall/VPN interfering \\n\");\n    }\n    return registryErrorExceptionBuilder.build();\n  }\n\n  /** Logs error message in red. */\n  private void logError(String message) {\n    eventHandlers.dispatch(LogEvent.error(\"\\u001B[31;1m\" + message + \"\\u001B[0m\"));\n  }\n\n  private void logErrorIfBrokenPipe(IOException ex) {\n    if (isBrokenPipe(ex)) {\n      logError(\n          \"broken pipe: the server shut down the connection. Check the server log if possible. \"\n              + \"This could also be a proxy issue. For example, a proxy may prevent sending \"\n              + \"packets that are too large.\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/**\n * Provides implementations for a registry endpoint. Implementations should be immutable.\n *\n * @param <T> the type returned from handling the endpoint response\n */\ninterface RegistryEndpointProvider<T> {\n\n  /** Returns the HTTP method to send the request with. */\n  String getHttpMethod();\n\n  /**\n   * Returns the registry endpoint URL.\n   *\n   * @param apiRouteBase the registry's base URL (for example, {@code https://gcr.io/v2/})\n   * @return the registry endpoint URL\n   */\n  URL getApiRoute(String apiRouteBase) throws MalformedURLException;\n\n  /** Returns the {@link BlobHttpContent} to send as the request body. */\n  @Nullable\n  BlobHttpContent getContent();\n\n  /** Returns a list of MIME types to pass as an HTTP {@code Accept} header. */\n  List<String> getAccept();\n\n  /** Handles the response specific to the registry action. */\n  T handleResponse(Response response) throws IOException, RegistryException;\n\n  /**\n   * Handles an {@link ResponseException} that occurs. Implementation must re-throw the given\n   * exception if it did not conclusively handled the response exception.\n   *\n   * @param responseException the {@link ResponseException} to handle\n   * @throws ResponseException {@code responseException} if {@code responseException} could not be\n   *     handled\n   * @throws RegistryErrorException if there is an error with a remote registry\n   */\n  T handleHttpResponseException(ResponseException responseException)\n      throws ResponseException, RegistryErrorException;\n\n  /**\n   * Returns description of the registry action performed, used in error messages to describe the\n   * action that failed.\n   */\n  String getActionDescription();\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointRequestProperties.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport javax.annotation.Nullable;\n\n/** Properties of registry endpoint requests. */\nclass RegistryEndpointRequestProperties {\n\n  private final String serverUrl;\n  private final String imageName;\n  @Nullable private final String sourceImageName;\n\n  /**\n   * New properties.\n   *\n   * @param serverUrl the server URL for the registry (for example, {@code gcr.io})\n   * @param imageName the image/repository name (also known as, namespace)\n   */\n  RegistryEndpointRequestProperties(String serverUrl, String imageName) {\n    this(serverUrl, imageName, null);\n  }\n\n  /**\n   * New properties.\n   *\n   * @param serverUrl the server URL for the registry (for example, {@code gcr.io})\n   * @param imageName the image/repository name (also known as, namespace)\n   * @param sourceImageName additional source image to request pull permission from the registry\n   */\n  RegistryEndpointRequestProperties(\n      String serverUrl, String imageName, @Nullable String sourceImageName) {\n    this.serverUrl = serverUrl;\n    this.imageName = imageName;\n    this.sourceImageName = sourceImageName;\n  }\n\n  String getServerUrl() {\n    return serverUrl;\n  }\n\n  String getImageName() {\n    return imageName;\n  }\n\n  @Nullable\n  String getSourceImageName() {\n    return sourceImageName;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryErrorException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport javax.annotation.Nullable;\n\n/**\n * Thrown when an HTTP request to a registry endpoint failed with errors as defined in {@link\n * ErrorCodes}.\n */\nclass RegistryErrorException extends RegistryException {\n\n  RegistryErrorException(String message, @Nullable Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryErrorExceptionBuilder.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;\nimport javax.annotation.Nullable;\n\n/** Builds a {@link RegistryErrorException} with multiple causes. */\nclass RegistryErrorExceptionBuilder {\n\n  @Nullable private final Throwable cause;\n  private final StringBuilder errorMessageBuilder = new StringBuilder();\n\n  private boolean firstErrorReason = true;\n\n  /**\n   * Gets the reason for certain errors.\n   *\n   * @param errorCodeString string form of {@link ErrorCodes}\n   * @param message the original received error message, which may or may not be used depending on\n   *     the {@code errorCode}\n   */\n  private static String getReason(@Nullable String errorCodeString, @Nullable String message) {\n    if (message == null) {\n      message = \"no details\";\n    }\n    if (errorCodeString == null) {\n      return \"unknown: \" + message;\n    }\n\n    try {\n      switch (ErrorCodes.valueOf(errorCodeString)) {\n        case MANIFEST_INVALID:\n        case BLOB_UNKNOWN:\n          return message + \" (something went wrong)\";\n\n        case MANIFEST_UNKNOWN:\n        case TAG_INVALID:\n        case MANIFEST_UNVERIFIED:\n          return message;\n\n        default:\n          return \"other: \" + message;\n      }\n    } catch (IllegalArgumentException ex) {\n      return \"unknown error code: \" + errorCodeString + \" (\" + message + \")\";\n    }\n  }\n\n  /** Creates a new builder with information about the method that errored. */\n  RegistryErrorExceptionBuilder(String method, @Nullable Throwable cause) {\n    this.cause = cause;\n\n    errorMessageBuilder.append(\"Tried to \");\n    errorMessageBuilder.append(method);\n    errorMessageBuilder.append(\" but failed because: \");\n  }\n\n  /** Creates a new builder with information about the method that errored. */\n  RegistryErrorExceptionBuilder(String method) {\n    this(method, null);\n  }\n\n  // TODO: Don't use a JsonTemplate as a data object to pass around.\n  /**\n   * Builds an entry to the error reasons from an {@link ErrorEntryTemplate}.\n   *\n   * @param errorEntry the {@link ErrorEntryTemplate} to add\n   */\n  RegistryErrorExceptionBuilder addReason(ErrorEntryTemplate errorEntry) {\n    String reason = getReason(errorEntry.getCode(), errorEntry.getMessage());\n    addReason(reason);\n    return this;\n  }\n\n  /** Adds an entry to the error reasons. */\n  RegistryErrorExceptionBuilder addReason(String reason) {\n    if (!firstErrorReason) {\n      errorMessageBuilder.append(\", \");\n    }\n    errorMessageBuilder.append(reason);\n    firstErrorReason = false;\n    return this;\n  }\n\n  RegistryErrorException build() {\n    return new RegistryErrorException(errorMessageBuilder.toString(), cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/UnexpectedBlobDigestException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.RegistryException;\n\n/** Thrown when a pulled BLOB did not have the same digest as requested. */\nclass UnexpectedBlobDigestException extends RegistryException {\n\n  UnexpectedBlobDigestException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/CredentialHelperNotFoundException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport java.nio.file.Path;\n\n/** Thrown because the requested credential helper CLI does not exist. */\npublic class CredentialHelperNotFoundException extends CredentialRetrievalException {\n\n  CredentialHelperNotFoundException(Path credentialHelper, Throwable cause) {\n    super(\"The system does not have \" + credentialHelper + \" CLI\", cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/CredentialHelperUnhandledServerUrlException.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport java.nio.file.Path;\n\n/** Thrown because the credential helper does not have credentials for the specified server URL. */\npublic class CredentialHelperUnhandledServerUrlException extends CredentialRetrievalException {\n\n  CredentialHelperUnhandledServerUrlException(\n      Path credentialHelper, String serverUrl, String credentialHelperOutput) {\n    super(\n        \"The credential helper (\"\n            + credentialHelper\n            + \") has nothing for server URL: \"\n            + serverUrl\n            + \"\\n\\nGot output:\\n\\n\"\n            + credentialHelperOutput);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/CredentialRetrievalException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\n\n/** Thrown if something went wrong during {@link CredentialRetriever#retrieve}. */\npublic class CredentialRetrievalException extends Exception {\n\n  CredentialRetrievalException(String message, Throwable cause) {\n    super(message, cause);\n  }\n\n  CredentialRetrievalException(String message) {\n    super(message);\n  }\n\n  public CredentialRetrievalException(Throwable cause) {\n    super(cause);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfig.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.google.cloud.tools.jib.registry.credentials.json.DockerConfigTemplate;\nimport com.google.cloud.tools.jib.registry.credentials.json.DockerConfigTemplate.AuthTemplate;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\n\n/** Handles getting useful information from a {@link DockerConfigTemplate}. */\nclass DockerConfig {\n\n  /**\n   * Returns the first entry matching the given key predicates (short-circuiting in the order of\n   * predicates).\n   */\n  @Nullable\n  private static <K, T> Map.Entry<K, T> findFirstInMapByKey(\n      Map<K, T> map, List<Predicate<K>> keyMatches) {\n    return keyMatches.stream()\n        .map(keyMatch -> findFirstInMapByKey(map, keyMatch))\n        .filter(Objects::nonNull)\n        .findFirst()\n        .orElse(null);\n  }\n\n  /** Returns the first entry matching the given key predicate. */\n  @Nullable\n  private static <K, T> Map.Entry<K, T> findFirstInMapByKey(Map<K, T> map, Predicate<K> keyMatch) {\n    return map.entrySet().stream()\n        .filter(entry -> keyMatch.test(entry.getKey()))\n        .findFirst()\n        .orElse(null);\n  }\n\n  private final DockerConfigTemplate dockerConfigTemplate;\n\n  DockerConfig(DockerConfigTemplate dockerConfigTemplate) {\n    this.dockerConfigTemplate = dockerConfigTemplate;\n  }\n\n  /**\n   * Returns the base64-encoded {@code Basic} authorization for {@code registry}, or {@code null} if\n   * none exists. The order of lookup preference:\n   *\n   * <ol>\n   *   <li>Exact registry name\n   *   <li>https:// + registry name\n   *   <li>registry name + / + arbitrary suffix\n   *   <li>https:// + registry name + / arbitrary suffix\n   * </ol>\n   *\n   * @param registry the registry to get the authorization for\n   * @return the base64-encoded {@code Basic} authorization for {@code registry}, or {@code null} if\n   *     none exists\n   */\n  @Nullable\n  AuthTemplate getAuthFor(String registry) {\n    Map.Entry<String, AuthTemplate> authEntry =\n        findFirstInMapByKey(dockerConfigTemplate.getAuths(), getRegistryMatchersFor(registry));\n    return authEntry != null ? authEntry.getValue() : null;\n  }\n\n  /**\n   * Determines a {@link DockerCredentialHelper} to use for {@code registry}.\n   *\n   * <p>If there exists a matching registry entry (or its aliases) in {@code credHelpers}, returns\n   * the corresponding credential helper. Otherwise, returns the credential helper defined by {@code\n   * credStore}.\n   *\n   * <p>See {@link #getRegistryMatchersFor} for the alias lookup order.\n   *\n   * @param registry the registry to get the credential helpers for\n   * @return the {@link DockerCredentialHelper} or {@code null} if none is found for the given\n   *     registry\n   */\n  @Nullable\n  DockerCredentialHelper getCredentialHelperFor(String registry) {\n    List<Predicate<String>> registryMatchers = getRegistryMatchersFor(registry);\n\n    Map.Entry<String, String> firstCredHelperMatch =\n        findFirstInMapByKey(dockerConfigTemplate.getCredHelpers(), registryMatchers);\n    if (firstCredHelperMatch != null) {\n      return new DockerCredentialHelper(\n          firstCredHelperMatch.getKey(),\n          Paths.get(\"docker-credential-\" + firstCredHelperMatch.getValue()));\n    }\n\n    if (dockerConfigTemplate.getCredsStore() != null) {\n      return new DockerCredentialHelper(\n          registry, Paths.get(\"docker-credential-\" + dockerConfigTemplate.getCredsStore()));\n    }\n\n    return null;\n  }\n\n  /**\n   * Gets registry matchers for a registry.\n   *\n   * <p>Matches are determined in the following order:\n   *\n   * <ol>\n   *   <li>Exact registry name\n   *   <li>https:// + registry name\n   *   <li>registry name + / + arbitrary suffix\n   *   <li>https:// + registry name + / + arbitrary suffix\n   * </ol>\n   *\n   * @param registry the registry to get matchers for\n   * @return the list of predicates to match possible aliases\n   */\n  private List<Predicate<String>> getRegistryMatchersFor(String registry) {\n    Predicate<String> exactMatch = registry::equals;\n    Predicate<String> withHttps = (\"https://\" + registry)::equals;\n    Predicate<String> withSuffix = name -> name.startsWith(registry + \"/\");\n    Predicate<String> withHttpsAndSuffix = name -> name.startsWith(\"https://\" + registry + \"/\");\n    return Arrays.asList(exactMatch, withHttps, withSuffix, withHttpsAndSuffix);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.MapperFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.json.JsonMapper;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.registry.RegistryAliasGroup;\nimport com.google.cloud.tools.jib.registry.credentials.json.DockerConfigTemplate;\nimport com.google.cloud.tools.jib.registry.credentials.json.DockerConfigTemplate.AuthTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Base64;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Consumer;\n\n/**\n * Retrieves registry credentials from the Docker config.\n *\n * <p>The credentials are searched in the following order (stopping when credentials are found):\n *\n * <ol>\n *   <li>The credential helper from {@code credHelpers} defined for a registry, if available.\n *   <li>The {@code credsStore} credential helper, if available.\n *   <li>If there is an {@code auth} defined for a registry.\n * </ol>\n *\n * @see <a\n *     href=\"https://docs.docker.com/engine/reference/commandline/login/\">https://docs.docker.com/engine/reference/commandline/login/</a>\n */\npublic class DockerConfigCredentialRetriever {\n\n  private final String registry;\n  private final Path dockerConfigFile;\n  private final boolean legacyConfigFormat;\n\n  public static DockerConfigCredentialRetriever create(String registry, Path dockerConfigFile) {\n    return new DockerConfigCredentialRetriever(registry, dockerConfigFile, false);\n  }\n\n  public static DockerConfigCredentialRetriever createForLegacyFormat(\n      String registry, Path dockerConfigFile) {\n    return new DockerConfigCredentialRetriever(registry, dockerConfigFile, true);\n  }\n\n  private DockerConfigCredentialRetriever(\n      String registry, Path dockerConfigFile, boolean legacyConfigFormat) {\n    this.registry = registry;\n    this.dockerConfigFile = dockerConfigFile;\n    this.legacyConfigFormat = legacyConfigFormat;\n  }\n\n  public Path getDockerConfigFile() {\n    return dockerConfigFile;\n  }\n\n  /**\n   * Retrieves credentials for a registry. Tries all possible known aliases.\n   *\n   * @param logger a consumer for handling log events\n   * @return {@link Credential} found for {@code registry}, or {@link Optional#empty} if not found\n   * @throws IOException if failed to parse the config JSON\n   */\n  public Optional<Credential> retrieve(Consumer<LogEvent> logger) throws IOException {\n    if (!Files.exists(dockerConfigFile)) {\n      return Optional.empty();\n    }\n\n    ObjectMapper objectMapper =\n        JsonMapper.builder()\n            .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)\n            .build();\n    try (InputStream fileIn = Files.newInputStream(dockerConfigFile)) {\n      if (legacyConfigFormat) {\n        // legacy config format is the value of the \"auths\":{ <map> } block of the new config (i.e.,\n        // the <map> of string -> DockerConfigTemplate.AuthTemplate).\n        Map<String, AuthTemplate> auths =\n            objectMapper.readValue(fileIn, new TypeReference<Map<String, AuthTemplate>>() {});\n        DockerConfig dockerConfig = new DockerConfig(new DockerConfigTemplate(auths));\n        return retrieve(dockerConfig, logger);\n      }\n\n      DockerConfig dockerConfig =\n          new DockerConfig(objectMapper.readValue(fileIn, DockerConfigTemplate.class));\n      return retrieve(dockerConfig, logger);\n    }\n  }\n\n  /**\n   * Retrieves credentials for a registry alias from a {@link DockerConfig}.\n   *\n   * @param dockerConfig the {@link DockerConfig} to retrieve from\n   * @param logger a consumer for handling log events\n   * @return the retrieved credentials, or {@code Optional#empty} if none are found\n   */\n  @VisibleForTesting\n  Optional<Credential> retrieve(DockerConfig dockerConfig, Consumer<LogEvent> logger) {\n    for (String registryAlias : RegistryAliasGroup.getAliasesGroup(registry)) {\n      // First, find a credential helper from \"credentialHelpers\" and \"credsStore\" in order.\n      DockerCredentialHelper dockerCredentialHelper =\n          dockerConfig.getCredentialHelperFor(registryAlias);\n      if (dockerCredentialHelper != null) {\n        try {\n          Path helperPath = dockerCredentialHelper.getCredentialHelper();\n          logger.accept(LogEvent.info(\"trying \" + helperPath + \" for \" + registryAlias));\n          // Tries with the given registry alias (may be the original registry).\n          return Optional.of(dockerCredentialHelper.retrieve());\n\n        } catch (IOException\n            | CredentialHelperUnhandledServerUrlException\n            | CredentialHelperNotFoundException ex) {\n          // Warns the user that the specified credential helper cannot be used.\n          if (ex.getMessage() != null) {\n            logger.accept(LogEvent.warn(ex.getMessage()));\n            if (ex.getCause() != null && ex.getCause().getMessage() != null) {\n              logger.accept(LogEvent.warn(\"  Caused by: \" + ex.getCause().getMessage()));\n            }\n          }\n        }\n      }\n\n      // Lastly, find defined auth.\n      AuthTemplate auth = dockerConfig.getAuthFor(registryAlias);\n      if (auth != null) {\n        if (auth.getAuth() != null) {\n          // 'auth' is a basic authentication token that should be parsed back into credentials\n          String usernameColonPassword =\n              new String(Base64.getDecoder().decode(auth.getAuth()), StandardCharsets.UTF_8);\n          String username = usernameColonPassword.substring(0, usernameColonPassword.indexOf(\":\"));\n          String password = usernameColonPassword.substring(usernameColonPassword.indexOf(\":\") + 1);\n          logger.accept(\n              LogEvent.info(\n                  \"Docker config auths section defines credentials for \" + registryAlias));\n          if (auth.getIdentityToken() != null\n              // These username and password checks may be unnecessary, but doing so to restrict the\n              // scope only to the Azure behavior to maintain maximum backward-compatibilty.\n              && username.equals(\"00000000-0000-0000-0000-000000000000\")\n              && password.isEmpty()) {\n            logger.accept(\n                LogEvent.info(\"Using 'identityToken' in Docker config auth for \" + registryAlias));\n            return Optional.of(\n                Credential.from(Credential.OAUTH2_TOKEN_USER_NAME, auth.getIdentityToken()));\n          }\n          return Optional.of(Credential.from(username, password));\n        } else if (auth.getUsername() != null && auth.getPassword() != null) {\n          logger.accept(\n              LogEvent.info(\n                  \"Docker config auths section defines username and password for \"\n                      + registryAlias));\n          return Optional.of(Credential.from(auth.getUsername(), auth.getPassword()));\n        }\n      }\n    }\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelper.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.io.CharStreams;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.function.Function;\nimport javax.annotation.Nullable;\n\n/**\n * Retrieves Docker credentials with a Docker credential helper.\n *\n * @see <a\n *     href=\"https://github.com/docker/docker-credential-helpers\">https://github.com/docker/docker-credential-helpers</a>\n */\npublic class DockerCredentialHelper {\n\n  private final String serverUrl;\n  private final Path credentialHelper;\n  private final Properties systemProperties;\n  private final Function<List<String>, ProcessBuilder> processBuilderFactory;\n  private final Map<String, String> environment;\n\n  /** Template for a Docker credential helper output. */\n  @VisibleForTesting\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  static class DockerCredentialsTemplate implements JsonTemplate {\n\n    @Nullable\n    @VisibleForTesting\n    @JsonProperty(\"Username\")\n    String username;\n\n    @Nullable\n    @VisibleForTesting\n    @JsonProperty(\"Secret\")\n    String secret;\n  }\n\n  /**\n   * Constructs a new {@link DockerCredentialHelper}.\n   *\n   * @param serverUrl the server URL to pass into the credential helper\n   * @param credentialHelper the path to the credential helper executable\n   */\n  public DockerCredentialHelper(String serverUrl, Path credentialHelper) {\n    this(\n        serverUrl,\n        credentialHelper,\n        System.getProperties(),\n        ProcessBuilder::new,\n        Collections.emptyMap());\n  }\n\n  /**\n   * Constructs a new {@link DockerCredentialHelper}.\n   *\n   * @param serverUrl the server URL to pass into the credential helper\n   * @param credentialHelper the path to the credential helper executable\n   * @param environment environment variables used in configuring the credential helper\n   */\n  public DockerCredentialHelper(\n      String serverUrl, Path credentialHelper, Map<String, String> environment) {\n    this(serverUrl, credentialHelper, System.getProperties(), ProcessBuilder::new, environment);\n  }\n\n  @VisibleForTesting\n  DockerCredentialHelper(\n      String serverUrl,\n      Path credentialHelper,\n      Properties systemProperties,\n      Function<List<String>, ProcessBuilder> processBuilderFactory,\n      Map<String, String> environment) {\n    this.serverUrl = serverUrl;\n    this.credentialHelper = credentialHelper;\n    this.systemProperties = systemProperties;\n    this.processBuilderFactory = processBuilderFactory;\n    this.environment = environment;\n  }\n\n  /**\n   * Calls the credential helper CLI.\n   *\n   * <p>Calls occur in the form:\n   *\n   * <pre>{@code\n   * echo -n <server URL> | docker-credential-<credential helper suffix> get\n   * }</pre>\n   *\n   * @return the Docker credentials by calling the corresponding CLI\n   * @throws IOException if writing/reading process input/output fails\n   * @throws CredentialHelperUnhandledServerUrlException if no credentials could be found for the\n   *     corresponding server\n   * @throws CredentialHelperNotFoundException if the credential helper CLI doesn't exist\n   */\n  public Credential retrieve()\n      throws IOException, CredentialHelperUnhandledServerUrlException,\n          CredentialHelperNotFoundException {\n    boolean isWindows =\n        systemProperties.getProperty(\"os.name\").toLowerCase(Locale.ENGLISH).contains(\"windows\");\n    String lowerCaseHelper = credentialHelper.toString().toLowerCase(Locale.ENGLISH);\n    if (!isWindows || lowerCaseHelper.endsWith(\".cmd\") || lowerCaseHelper.endsWith(\".exe\")) {\n      return retrieve(Arrays.asList(credentialHelper.toString(), \"get\"));\n    }\n\n    // We are on Windows with undefined/unknown file extension.\n    for (String suffix : Arrays.asList(\".cmd\", \".exe\")) {\n      try {\n        return retrieve(Arrays.asList(credentialHelper.toString() + suffix, \"get\"));\n      } catch (CredentialHelperNotFoundException ignored) {\n        // ignored\n      }\n    }\n    // On Windows, launching a process from Java without a file extension should normally fail\n    // (https://github.com/GoogleContainerTools/jib/issues/2399#issuecomment-612972912), but\n    // running Jib on Linux-like environment (e.g., Cygwin) might succeed?\n    return retrieve(Arrays.asList(credentialHelper.toString(), \"get\"));\n  }\n\n  private Credential retrieve(List<String> credentialHelperCommand)\n      throws IOException, CredentialHelperUnhandledServerUrlException,\n          CredentialHelperNotFoundException {\n    try {\n      ProcessBuilder processBuilder = processBuilderFactory.apply(credentialHelperCommand);\n      processBuilder.environment().putAll(environment);\n      Process process = processBuilder.start();\n\n      try (OutputStream processStdin = process.getOutputStream()) {\n        processStdin.write(serverUrl.getBytes(StandardCharsets.UTF_8));\n      }\n\n      try (InputStreamReader processStdoutReader =\n          new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)) {\n        String output = CharStreams.toString(processStdoutReader);\n\n        // Throws an exception if the credential store does not have credentials for serverUrl.\n        if (output.contains(\"credentials not found in native keychain\")) {\n          throw new CredentialHelperUnhandledServerUrlException(\n              credentialHelper, serverUrl, output);\n        }\n        if (output.isEmpty()) {\n          try (InputStreamReader processStderrReader =\n              new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8)) {\n            String errorOutput = CharStreams.toString(processStderrReader);\n            throw new CredentialHelperUnhandledServerUrlException(\n                credentialHelper, serverUrl, errorOutput);\n          }\n        }\n\n        try {\n          DockerCredentialsTemplate dockerCredentials =\n              JsonTemplateMapper.readJson(output, DockerCredentialsTemplate.class);\n          if (Strings.isNullOrEmpty(dockerCredentials.username)\n              || Strings.isNullOrEmpty(dockerCredentials.secret)) {\n            throw new CredentialHelperUnhandledServerUrlException(\n                credentialHelper, serverUrl, output);\n          }\n\n          return Credential.from(dockerCredentials.username, dockerCredentials.secret);\n\n        } catch (JsonProcessingException ex) {\n          throw new CredentialHelperUnhandledServerUrlException(\n              credentialHelper, serverUrl, output);\n        }\n      }\n\n    } catch (IOException ex) {\n      if (ex.getMessage() == null) {\n        throw ex;\n      }\n\n      // Checks if the failure is due to a nonexistent credential helper CLI.\n      if (ex.getMessage().contains(\"No such file or directory\")\n          || ex.getMessage().contains(\"cannot find the file\")\n          || ex.getMessage().contains(\"error=2\")) /* errno=2 (ENOENT) */ {\n        throw new CredentialHelperNotFoundException(credentialHelper, ex);\n      }\n\n      throw ex;\n    }\n  }\n\n  Path getCredentialHelper() {\n    return credentialHelper;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/json/DockerConfigTemplate.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\n/**\n * Template for a Docker config file.\n *\n * <p>Example:\n *\n * <pre>{@code\n * {\n *   \"auths\": {\n *     \"registry\": {\n *       \"auth\": \"username:password string in base64\",\n *       \"username\": \"...\"\n *       \"password\": \"...\"\n *       \"identityToken\": \"...\"\n *     },\n *     \"anotherregistry\": {},\n *     ...\n *   },\n *   \"credsStore\": \"legacy credential helper config acting as a \\\"default\\\" helper\",\n *   \"credHelpers\": {\n *     \"registry\": \"credential helper name\",\n *     \"anotherregistry\": \"another credential helper name\",\n *     ...\n *   }\n * }\n * }</pre>\n *\n * <p>Each entry in {@code credHelpers} is a mapping from a registry to a credential helper that\n * stores the authorization for that registry. This takes precedence over {@code credsStore} if\n * there exists a match.\n *\n * <p>{@code credsStore} is a legacy config that acts to provide a \"default\" credential helper if\n * there is no match in {@code credHelpers}.\n *\n * <p>If an {@code auth} is defined for a registry, that is a valid {@code Basic} authorization to\n * use for that registry.\n */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class DockerConfigTemplate implements JsonTemplate {\n\n  /** Template for an {@code auth} defined for a registry under {@code auths}. */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  public static class AuthTemplate implements JsonTemplate {\n\n    @Nullable private String auth;\n\n    @Nullable private String username;\n\n    @Nullable private String password;\n\n    // Both \"identitytoken\" and \"identityToken\" have been observed. For example,\n    // https://github.com/GoogleContainerTools/jib/issues/2488\n    // https://github.com/spotify/docker-client/issues/580\n    @Nullable private String identityToken;\n\n    @Nullable\n    public String getAuth() {\n      return auth;\n    }\n\n    @Nullable\n    public String getUsername() {\n      return username;\n    }\n\n    @Nullable\n    public String getPassword() {\n      return password;\n    }\n\n    @Nullable\n    public String getIdentityToken() {\n      return identityToken;\n    }\n  }\n\n  /** Maps from registry to its {@link AuthTemplate}. */\n  private final Map<String, AuthTemplate> auths;\n\n  @Nullable private String credsStore;\n\n  /** Maps from registry to credential helper name. */\n  private final Map<String, String> credHelpers = new HashMap<>();\n\n  public DockerConfigTemplate(Map<String, AuthTemplate> auths) {\n    this.auths = ImmutableMap.copyOf(auths);\n  }\n\n  @SuppressWarnings(\"unused\")\n  private DockerConfigTemplate() {\n    auths = new HashMap<>();\n  }\n\n  public Map<String, AuthTemplate> getAuths() {\n    return auths;\n  }\n\n  @Nullable\n  public String getCredsStore() {\n    return credsStore;\n  }\n\n  public Map<String, String> getCredHelpers() {\n    return credHelpers;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/json/ErrorEntryTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.json;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport javax.annotation.Nullable;\n\n// TODO: Should include detail field as well - need to have custom parser\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class ErrorEntryTemplate implements JsonTemplate {\n\n  @Nullable private String code;\n  @Nullable private String message;\n\n  public ErrorEntryTemplate(String code, String message) {\n    this.code = code;\n    this.message = message;\n  }\n\n  /** Necessary for Jackson to create from JSON. */\n  @SuppressWarnings(\"unused\")\n  private ErrorEntryTemplate() {}\n\n  @Nullable\n  public String getCode() {\n    return code;\n  }\n\n  @Nullable\n  public String getMessage() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/registry/json/ErrorResponseTemplate.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.json;\n\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Template for the registry response body JSON when a request errored.\n *\n * <p>Example:\n *\n * <pre>{@code\n * {\n *   \"errors\": [\n *     {\n *       \"code\": \"MANIFEST_UNKNOWN\",\n *       \"message\": \"manifest unknown\",\n *       \"detail\": {\"Tag\": \"latest\"}\n *     }\n *   ]\n * }\n * }</pre>\n */\npublic class ErrorResponseTemplate implements JsonTemplate {\n\n  private final List<ErrorEntryTemplate> errors = new ArrayList<>();\n\n  public List<ErrorEntryTemplate> getErrors() {\n    return Collections.unmodifiableList(errors);\n  }\n\n  @VisibleForTesting\n  public ErrorResponseTemplate addError(ErrorEntryTemplate errorEntryTemplate) {\n    errors.add(errorEntryTemplate);\n    return this;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarExtractor.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.tar;\n\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.io.ByteStreams;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileTime;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\n\n/** Extracts a tarball. */\npublic class TarExtractor {\n\n  private TarExtractor() {}\n\n  /**\n   * Extracts a tarball to the specified destination.\n   *\n   * @param source the tarball to extract\n   * @param destination the output directory\n   * @throws IOException if extraction fails\n   */\n  public static void extract(Path source, Path destination) throws IOException {\n    extract(source, destination, false);\n  }\n\n  /**\n   * Extracts a tarball to the specified destination.\n   *\n   * @param source the tarball to extract\n   * @param destination the output directory\n   * @param enableReproducibleTimestamps whether or not reproducible timestamps should be used\n   * @throws IOException if extraction fails\n   * @throws IllegalStateException when reproducible timestamps are enabled but the target root used\n   *     for extracting the tar contents is not empty\n   */\n  public static void extract(Path source, Path destination, boolean enableReproducibleTimestamps)\n      throws IOException {\n    if (enableReproducibleTimestamps\n        && Files.isDirectory(destination)\n        && destination.toFile().list().length != 0) {\n      throw new IllegalStateException(\n          \"Cannot enable reproducible timestamps. They can only be enabled when the target root doesn't exist or is an empty directory\");\n    }\n    String canonicalDestination = destination.toFile().getCanonicalPath();\n    List<TarArchiveEntry> entries = new ArrayList<>();\n    try (InputStream in = new BufferedInputStream(Files.newInputStream(source));\n        TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(in)) {\n      for (TarArchiveEntry entry = tarArchiveInputStream.getNextEntry();\n          entry != null;\n          entry = tarArchiveInputStream.getNextEntry()) {\n        entries.add(entry);\n        Path entryPath = destination.resolve(entry.getName());\n\n        String canonicalTarget = entryPath.toFile().getCanonicalPath();\n        if (!canonicalTarget.startsWith(canonicalDestination + File.separator)) {\n          String offender = entry.getName() + \" from \" + source;\n          throw new IOException(\"Blocked unzipping files outside destination: \" + offender);\n        }\n        if (entry.isDirectory()) {\n          Files.createDirectories(entryPath);\n        } else {\n          if (entryPath.getParent() != null) {\n            Files.createDirectories(entryPath.getParent());\n          }\n\n          if (entry.isSymbolicLink()) {\n            Files.createSymbolicLink(entryPath, Paths.get(entry.getLinkName()));\n          } else {\n            try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(entryPath))) {\n              ByteStreams.copy(tarArchiveInputStream, out);\n            }\n          }\n        }\n      }\n    }\n    preserveModificationTimes(destination, entries, enableReproducibleTimestamps);\n  }\n\n  /**\n   * Preserve modification timestamps of files and directories in a tar file. If a directory is not\n   * an entry in the tar file and reproducible timestamps are enabled then its modification\n   * timestamp is set to a constant value. Note that the modification timestamps of symbolic links\n   * are not preserved even with reproducible timestamps enabled.\n   *\n   * @param destination target root for unzipping\n   * @param entries list of entries in tar file\n   * @param enableReproducibleTimestamps whether or not reproducible timestamps should be used\n   * @throws IOException when I/O error occurs\n   */\n  private static void preserveModificationTimes(\n      Path destination, List<TarArchiveEntry> entries, boolean enableReproducibleTimestamps)\n      throws IOException {\n    if (enableReproducibleTimestamps) {\n      FileTime epochPlusOne = FileTime.fromMillis(1000L);\n      new DirectoryWalker(destination)\n          .filter(Files::isDirectory)\n          .walk(path -> Files.setLastModifiedTime(path, epochPlusOne));\n    }\n    for (TarArchiveEntry entry : entries) {\n\n      // Setting the symbolic link's modification timestamp will cause the modification timestamp of\n      // the target to change\n      if (!entry.isSymbolicLink()) {\n        Files.setLastModifiedTime(\n            destination.resolve(entry.getName()), FileTime.from(entry.getModTime().toInstant()));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarStreamBuilder.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.tar;\n\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Instant;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;\n\n/** Builds a tarball archive. */\npublic class TarStreamBuilder {\n\n  /**\n   * Maps from {@link TarArchiveEntry} to a {@link Blob}. The order of the entries is the order they\n   * belong in the tarball.\n   */\n  private final LinkedHashMap<TarArchiveEntry, Blob> archiveMap = new LinkedHashMap<>();\n\n  /**\n   * Writes each entry in the filesystem to the tarball archive stream.\n   *\n   * @param out the stream to write to.\n   * @throws IOException if building the tarball fails.\n   */\n  public void writeAsTarArchiveTo(OutputStream out) throws IOException {\n    try (TarArchiveOutputStream tarArchiveOutputStream =\n        new TarArchiveOutputStream(out, StandardCharsets.UTF_8.name())) {\n      // Enables PAX extended headers to support long file names.\n      tarArchiveOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);\n      tarArchiveOutputStream.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);\n      for (Map.Entry<TarArchiveEntry, Blob> entry : archiveMap.entrySet()) {\n        tarArchiveOutputStream.putArchiveEntry(entry.getKey());\n        entry.getValue().writeTo(tarArchiveOutputStream);\n        tarArchiveOutputStream.closeArchiveEntry();\n      }\n    }\n  }\n\n  /**\n   * Adds a {@link TarArchiveEntry} to the archive.\n   *\n   * @param entry the {@link TarArchiveEntry}\n   */\n  public void addTarArchiveEntry(TarArchiveEntry entry) {\n    archiveMap.put(\n        entry, entry.isFile() ? Blobs.from(entry.getPath()) : Blobs.from(ignored -> {}, true));\n  }\n\n  /**\n   * Adds a blob to the archive. Note that this should be used with raw bytes and not file contents;\n   * for adding files to the archive, use {@link #addTarArchiveEntry}.\n   *\n   * @param contents the bytes to add to the tarball\n   * @param name the name of the entry (i.e. filename)\n   * @param modificationTime the modification time of the entry\n   */\n  public void addByteEntry(byte[] contents, String name, Instant modificationTime) {\n    TarArchiveEntry entry = new TarArchiveEntry(name);\n    entry.setSize(contents.length);\n    entry.setModTime(modificationTime.toEpochMilli());\n    archiveMap.put(entry, Blobs.from(outputStream -> outputStream.write(contents), true));\n  }\n\n  /**\n   * Adds a blob to the archive. Note that this should be used with non-file {@link Blob}s; for\n   * adding files to the archive, use {@link #addTarArchiveEntry}.\n   *\n   * @param blob the {@link Blob} to add to the tarball\n   * @param size the size (in bytes) of {@code blob}\n   * @param name the name of the entry (i.e. filename)\n   * @param modificationTime the modification time of the entry\n   */\n  public void addBlobEntry(Blob blob, long size, String name, Instant modificationTime) {\n    TarArchiveEntry entry = new TarArchiveEntry(name);\n    entry.setSize(size);\n    entry.setModTime(modificationTime.toEpochMilli());\n    archiveMap.put(entry, blob);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/main/resources/commons-logging.properties",
    "content": "org.apache.commons.logging.Log=org.apache.commons.logging.impl.NoOpLog\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/MultithreadedExecutor.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.Assert;\n\n/** Testing infrastructure for running code across multiple threads. */\npublic class MultithreadedExecutor implements Closeable {\n\n  private static final Duration MULTITHREADED_TEST_TIMEOUT = Duration.ofSeconds(3);\n  private static final int THREAD_COUNT = 20;\n\n  private final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);\n\n  public <E> E invoke(Callable<E> callable) throws ExecutionException, InterruptedException {\n    List<E> returnValue = invokeAll(Collections.singletonList(callable));\n    return returnValue.get(0);\n  }\n\n  public <E> List<E> invokeAll(List<Callable<E>> callables)\n      throws InterruptedException, ExecutionException {\n    List<Future<E>> futures =\n        executorService.invokeAll(\n            callables, MULTITHREADED_TEST_TIMEOUT.getSeconds(), TimeUnit.SECONDS);\n\n    List<E> returnValues = new ArrayList<>();\n    for (Future<E> future : futures) {\n      Assert.assertTrue(future.isDone());\n      returnValues.add(future.get());\n    }\n\n    return returnValues;\n  }\n\n  @Override\n  public void close() throws IOException {\n    executorService.shutdown();\n    try {\n      executorService.awaitTermination(MULTITHREADED_TEST_TIMEOUT.getSeconds(), TimeUnit.SECONDS);\n\n    } catch (InterruptedException ex) {\n      throw new IOException(ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/ContainerizerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.docker.AnotherDockerClient;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.concurrent.ExecutorService;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\n/** Tests for {@link Containerizer}. */\npublic class ContainerizerTest {\n\n  @Test\n  public void testTo() throws CacheDirectoryCreationException, InvalidImageReferenceException {\n    RegistryImage registryImage = RegistryImage.named(\"registry/image\");\n    DockerDaemonImage dockerDaemonImage = DockerDaemonImage.named(\"daemon/image\");\n    TarImage tarImage = TarImage.at(Paths.get(\"ignored\")).named(\"tar/image\");\n    DockerClient dockerClient = new AnotherDockerClient();\n\n    verifyTo(Containerizer.to(registryImage));\n    verifyTo(Containerizer.to(dockerDaemonImage));\n    verifyTo(Containerizer.to(tarImage));\n    verifyTo(Containerizer.to(dockerClient, dockerDaemonImage));\n  }\n\n  private void verifyTo(Containerizer containerizer) throws CacheDirectoryCreationException {\n    Assert.assertTrue(containerizer.getAdditionalTags().isEmpty());\n    Assert.assertFalse(containerizer.getExecutorService().isPresent());\n    Assert.assertEquals(\n        Containerizer.DEFAULT_BASE_CACHE_DIRECTORY,\n        containerizer.getBaseImageLayersCacheDirectory());\n    Assert.assertNotEquals(\n        Containerizer.DEFAULT_BASE_CACHE_DIRECTORY,\n        containerizer.getApplicationLayersCacheDirectory());\n    Assert.assertFalse(containerizer.getAllowInsecureRegistries());\n    Assert.assertEquals(\"jib-core\", containerizer.getToolName());\n\n    ExecutorService executorService = MoreExecutors.newDirectExecutorService();\n    containerizer\n        .withAdditionalTag(\"tag1\")\n        .withAdditionalTag(\"tag2\")\n        .setExecutorService(executorService)\n        .setBaseImageLayersCache(Paths.get(\"base/image/layers\"))\n        .setApplicationLayersCache(Paths.get(\"application/layers\"))\n        .setAllowInsecureRegistries(true)\n        .setToolName(\"tool\");\n\n    Assert.assertEquals(ImmutableSet.of(\"tag1\", \"tag2\"), containerizer.getAdditionalTags());\n    Assert.assertTrue(containerizer.getExecutorService().isPresent());\n    Assert.assertSame(executorService, containerizer.getExecutorService().get());\n    Assert.assertEquals(\n        Paths.get(\"base/image/layers\"), containerizer.getBaseImageLayersCacheDirectory());\n    Assert.assertEquals(\n        Paths.get(\"application/layers\"), containerizer.getApplicationLayersCacheDirectory());\n    Assert.assertTrue(containerizer.getAllowInsecureRegistries());\n    Assert.assertEquals(\"tool\", containerizer.getToolName());\n  }\n\n  @Test\n  public void testWithAdditionalTag() throws InvalidImageReferenceException {\n    Containerizer containerizer = Containerizer.to(DockerDaemonImage.named(\"image\"));\n\n    containerizer.withAdditionalTag(\"tag\");\n    try {\n      containerizer.withAdditionalTag(\"+invalid+\");\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"invalid tag '+invalid+'\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testGetImageConfiguration_registryImage() throws InvalidImageReferenceException {\n    CredentialRetriever credentialRetriever = Mockito.mock(CredentialRetriever.class);\n    Containerizer containerizer =\n        Containerizer.to(\n            RegistryImage.named(\"registry/image\").addCredentialRetriever(credentialRetriever));\n\n    ImageConfiguration imageConfiguration = containerizer.getImageConfiguration();\n    Assert.assertEquals(\"registry/image\", imageConfiguration.getImage().toString());\n    Assert.assertEquals(\n        Arrays.asList(credentialRetriever), imageConfiguration.getCredentialRetrievers());\n  }\n\n  @Test\n  public void testGetImageConfiguration_dockerDaemonImage() throws InvalidImageReferenceException {\n    Containerizer containerizer = Containerizer.to(DockerDaemonImage.named(\"docker/daemon/image\"));\n\n    ImageConfiguration imageConfiguration = containerizer.getImageConfiguration();\n    Assert.assertEquals(\"docker/daemon/image\", imageConfiguration.getImage().toString());\n    Assert.assertEquals(0, imageConfiguration.getCredentialRetrievers().size());\n  }\n\n  @Test\n  public void testGetImageConfiguration_tarImage() throws InvalidImageReferenceException {\n    Containerizer containerizer =\n        Containerizer.to(TarImage.at(Paths.get(\"output/file\")).named(\"tar/image\"));\n\n    ImageConfiguration imageConfiguration = containerizer.getImageConfiguration();\n    Assert.assertEquals(\"tar/image\", imageConfiguration.getImage().toString());\n    Assert.assertEquals(0, imageConfiguration.getCredentialRetrievers().size());\n  }\n\n  @Test\n  public void testGetApplicationLayersCacheDirectory_defaults()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException, IOException {\n    Containerizer containerizer = Containerizer.to(RegistryImage.named(\"registry/image\"));\n    Path applicationLayersCache = containerizer.getApplicationLayersCacheDirectory();\n    Path expectedCacheDir =\n        Paths.get(System.getProperty(\"java.io.tmpdir\"))\n            .resolve(\"jib-core-application-layers-cache\");\n    Assert.assertTrue(Files.isSameFile(expectedCacheDir, applicationLayersCache));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/CredentialTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link com.google.cloud.tools.jib.api.Credential}. */\npublic class CredentialTest {\n\n  @Test\n  public void testCredentialsHash() {\n    Credential credentialA1 = Credential.from(\"username\", \"password\");\n    Credential credentialA2 = Credential.from(\"username\", \"password\");\n    Credential credentialB1 = Credential.from(\"\", \"\");\n    Credential credentialB2 = Credential.from(\"\", \"\");\n\n    Assert.assertEquals(credentialA1, credentialA2);\n    Assert.assertEquals(credentialB1, credentialB2);\n    Assert.assertNotEquals(credentialA1, credentialB1);\n    Assert.assertNotEquals(credentialA1, credentialB2);\n\n    Set<Credential> credentialSet =\n        new HashSet<>(Arrays.asList(credentialA1, credentialA2, credentialB1, credentialB2));\n    Assert.assertEquals(new HashSet<>(Arrays.asList(credentialA2, credentialB1)), credentialSet);\n  }\n\n  @Test\n  public void testCredentialsOAuth2RefreshToken() {\n    Credential oauth2Credential = Credential.from(\"<token>\", \"eyJhbGciOi...3gw\");\n    Assert.assertTrue(\n        \"Credential should be an auth2 token when username is <token>\",\n        oauth2Credential.isOAuth2RefreshToken());\n    Assert.assertEquals(\n        \"OAuth2 token credential should take password as refresh token\",\n        \"eyJhbGciOi...3gw\",\n        oauth2Credential.getPassword());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/DescriptorDigestTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.security.DigestException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link DescriptorDigest}. */\npublic class DescriptorDigestTest {\n\n  @Test\n  public void testCreateFromHash_pass() throws DigestException {\n    String goodHash = createGoodHash('a');\n\n    DescriptorDigest descriptorDigest = DescriptorDigest.fromHash(goodHash);\n\n    Assert.assertEquals(goodHash, descriptorDigest.getHash());\n    Assert.assertEquals(\"sha256:\" + goodHash, descriptorDigest.toString());\n  }\n\n  @Test\n  public void testCreateFromHash_fail() {\n    String badHash = \"not a valid hash\";\n\n    try {\n      DescriptorDigest.fromHash(badHash);\n      Assert.fail(\"Invalid hash should have caused digest creation failure.\");\n    } catch (DigestException ex) {\n      Assert.assertEquals(\"Invalid hash: \" + badHash, ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCreateFromHash_failIncorrectLength() {\n    String badHash = createGoodHash('a') + 'a';\n\n    try {\n      DescriptorDigest.fromHash(badHash);\n      Assert.fail(\"Invalid hash should have caused digest creation failure.\");\n    } catch (DigestException ex) {\n      Assert.assertEquals(\"Invalid hash: \" + badHash, ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCreateFromDigest_pass() throws DigestException {\n    String goodHash = createGoodHash('a');\n    String goodDigest = \"sha256:\" + createGoodHash('a');\n\n    DescriptorDigest descriptorDigest = DescriptorDigest.fromDigest(goodDigest);\n\n    Assert.assertEquals(goodHash, descriptorDigest.getHash());\n    Assert.assertEquals(goodDigest, descriptorDigest.toString());\n  }\n\n  @Test\n  public void testCreateFromDigest_fail() {\n    String badDigest = \"sha256:not a valid digest\";\n\n    try {\n      DescriptorDigest.fromDigest(badDigest);\n      Assert.fail(\"Invalid digest should have caused digest creation failure.\");\n    } catch (DigestException ex) {\n      Assert.assertEquals(\"Invalid digest: \" + badDigest, ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testUseAsMapKey() throws DigestException {\n    DescriptorDigest descriptorDigestA1 = DescriptorDigest.fromHash(createGoodHash('a'));\n    DescriptorDigest descriptorDigestA2 = DescriptorDigest.fromHash(createGoodHash('a'));\n    DescriptorDigest descriptorDigestA3 =\n        DescriptorDigest.fromDigest(\"sha256:\" + createGoodHash('a'));\n    DescriptorDigest descriptorDigestB = DescriptorDigest.fromHash(createGoodHash('b'));\n\n    Map<DescriptorDigest, String> digestMap = new HashMap<>();\n\n    digestMap.put(descriptorDigestA1, \"digest with a\");\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA1));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA2));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA3));\n    Assert.assertNull(digestMap.get(descriptorDigestB));\n\n    digestMap.put(descriptorDigestA2, \"digest with a\");\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA1));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA2));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA3));\n    Assert.assertNull(digestMap.get(descriptorDigestB));\n\n    digestMap.put(descriptorDigestA3, \"digest with a\");\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA1));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA2));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA3));\n    Assert.assertNull(digestMap.get(descriptorDigestB));\n\n    digestMap.put(descriptorDigestB, \"digest with b\");\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA1));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA2));\n    Assert.assertEquals(\"digest with a\", digestMap.get(descriptorDigestA3));\n    Assert.assertEquals(\"digest with b\", digestMap.get(descriptorDigestB));\n  }\n\n  /** Creates a 32 byte hexademical string to fit valid hash pattern. */\n  private static String createGoodHash(char character) {\n    StringBuilder goodHashBuffer = new StringBuilder(64);\n    for (int i = 0; i < 64; i++) {\n      goodHashBuffer.append(character);\n    }\n    return goodHashBuffer.toString();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerClientResolverTest.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.docker.AnotherDockerClient;\nimport com.google.cloud.tools.jib.docker.DockerClientResolver;\nimport java.util.Collections;\nimport java.util.Optional;\nimport org.junit.Test;\n\n/** Tests for {@link DockerClientResolver}. */\npublic class DockerClientResolverTest {\n\n  @Test\n  public void testDockerClientIsReturned() {\n    Optional<DockerClient> dockerClient =\n        DockerClientResolver.resolve(Collections.singletonMap(\"test\", \"true\"));\n    assertThat(dockerClient.get()).isInstanceOf(AnotherDockerClient.class);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerDaemonImageTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport com.google.common.collect.ImmutableMap;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link DockerDaemonImage}. */\npublic class DockerDaemonImageTest {\n\n  @Test\n  public void testGetters_default() throws InvalidImageReferenceException {\n    DockerDaemonImage dockerDaemonImage = DockerDaemonImage.named(\"docker/daemon/image\");\n\n    Assert.assertEquals(\"docker/daemon/image\", dockerDaemonImage.getImageReference().toString());\n    Assert.assertEquals(\n        CliDockerClient.DEFAULT_DOCKER_CLIENT, dockerDaemonImage.getDockerExecutable());\n    Assert.assertEquals(0, dockerDaemonImage.getDockerEnvironment().size());\n  }\n\n  @Test\n  public void testGetters() throws InvalidImageReferenceException {\n    DockerDaemonImage dockerDaemonImage =\n        DockerDaemonImage.named(\"docker/daemon/image\")\n            .setDockerExecutable(Paths.get(\"docker/binary\"))\n            .setDockerEnvironment(ImmutableMap.of(\"key\", \"value\"));\n\n    Assert.assertEquals(Paths.get(\"docker/binary\"), dockerDaemonImage.getDockerExecutable());\n    Assert.assertEquals(ImmutableMap.of(\"key\", \"value\"), dockerDaemonImage.getDockerEnvironment());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/ImageReferenceTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.common.base.Strings;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ImageReference}. */\npublic class ImageReferenceTest {\n\n  private static final List<String> goodRegistries =\n      Arrays.asList(\"some.domain---name.123.com:8080\", \"gcr.io\", \"localhost\", null, \"\");\n  private static final List<String> goodRepositories =\n      Arrays.asList(\"some123_abc/repository__123-456/name---here\", \"distroless/java\", \"repository\");\n  private static final List<String> goodTags = Arrays.asList(\"some-.-.Tag\", \"\", \"latest\", null);\n  private static final List<String> goodDigests =\n      Arrays.asList(\n          \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", null);\n\n  private static final List<String> badImageReferences =\n      Arrays.asList(\n          \"\",\n          \":justsometag\",\n          \"@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n          \"repository@sha256:a\",\n          \"repository@notadigest\",\n          \"Repositorywithuppercase\",\n          \"registry:8080/Repositorywithuppercase/repository:sometag\",\n          \"domain.name:nonnumberport/repository\",\n          \"domain.name:nonnumberport//:no-repository\");\n\n  @Test\n  public void testParse_pass() throws InvalidImageReferenceException {\n    for (String goodRegistry : goodRegistries) {\n      for (String goodRepository : goodRepositories) {\n        for (String goodTag : goodTags) {\n          for (String goodDigest : goodDigests) {\n            verifyParse(goodRegistry, goodRepository, goodTag, goodDigest);\n          }\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testParse_dockerHub_official() throws InvalidImageReferenceException {\n    String imageReferenceString = \"busybox\";\n    ImageReference imageReference = ImageReference.parse(imageReferenceString);\n\n    Assert.assertEquals(\"registry-1.docker.io\", imageReference.getRegistry());\n    Assert.assertEquals(\"library/busybox\", imageReference.getRepository());\n    Assert.assertEquals(\"latest\", imageReference.getTag().orElse(null));\n  }\n\n  @Test\n  public void testParse_dockerHub_user() throws InvalidImageReferenceException {\n    String imageReferenceString = \"someuser/someimage\";\n    ImageReference imageReference = ImageReference.parse(imageReferenceString);\n\n    Assert.assertEquals(\"registry-1.docker.io\", imageReference.getRegistry());\n    Assert.assertEquals(\"someuser/someimage\", imageReference.getRepository());\n    Assert.assertEquals(\"latest\", imageReference.getTag().orElse(null));\n  }\n\n  @Test\n  public void testParse_invalid() {\n    for (String badImageReference : badImageReferences) {\n      try {\n        ImageReference.parse(badImageReference);\n        Assert.fail(badImageReference + \" should not be a valid image reference\");\n\n      } catch (InvalidImageReferenceException ex) {\n        MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.containsString(badImageReference));\n      }\n    }\n  }\n\n  @Test\n  public void testOf_smoke() {\n    String expectedRegistry = \"someregistry\";\n    String expectedRepository = \"somerepository\";\n    String expectedTag = \"sometag\";\n\n    Assert.assertEquals(\n        expectedRegistry,\n        ImageReference.of(expectedRegistry, expectedRepository, expectedTag).getRegistry());\n    Assert.assertEquals(\n        expectedRepository,\n        ImageReference.of(expectedRegistry, expectedRepository, expectedTag).getRepository());\n    Assert.assertEquals(\n        expectedTag,\n        ImageReference.of(expectedRegistry, expectedRepository, expectedTag).getTag().orElse(null));\n    Assert.assertEquals(\n        \"registry-1.docker.io\",\n        ImageReference.of(null, expectedRepository, expectedTag).getRegistry());\n    Assert.assertEquals(\n        \"registry-1.docker.io\", ImageReference.of(null, expectedRepository, null).getRegistry());\n    Assert.assertEquals(\n        \"latest\",\n        ImageReference.of(expectedRegistry, expectedRepository, null).getTag().orElse(null));\n    Assert.assertEquals(\n        \"latest\", ImageReference.of(null, expectedRepository, null).getTag().orElse(null));\n    Assert.assertEquals(\n        expectedRepository, ImageReference.of(null, expectedRepository, null).getRepository());\n  }\n\n  @Test\n  public void testToString() throws InvalidImageReferenceException {\n    Assert.assertEquals(\"someimage\", ImageReference.of(null, \"someimage\", null).toString());\n    Assert.assertEquals(\"someimage\", ImageReference.of(\"\", \"someimage\", \"\").toString());\n    Assert.assertEquals(\n        \"someotherimage\", ImageReference.of(null, \"library/someotherimage\", null).toString());\n    Assert.assertEquals(\n        \"someregistry/someotherimage\",\n        ImageReference.of(\"someregistry\", \"someotherimage\", null).toString());\n    Assert.assertEquals(\n        \"anotherregistry/anotherimage:sometag\",\n        ImageReference.of(\"anotherregistry\", \"anotherimage\", \"sometag\").toString());\n\n    Assert.assertEquals(\n        \"someimage@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n        ImageReference.of(\n                null,\n                \"someimage\",\n                \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")\n            .toString());\n    Assert.assertEquals(\n        \"gcr.io/distroless/java@sha256:b430543bea1d8326e767058bdab3a2482ea45f59d7af5c5c61334cd29ede88a1\",\n        ImageReference.parse(\n                \"gcr.io/distroless/java@sha256:b430543bea1d8326e767058bdab3a2482ea45f59d7af5c5c61334cd29ede88a1\")\n            .toString());\n  }\n\n  @Test\n  public void testToStringWithQualifier() {\n    Assert.assertEquals(\n        \"someimage:latest\", ImageReference.of(null, \"someimage\", null).toStringWithQualifier());\n    Assert.assertEquals(\n        \"someimage:latest\", ImageReference.of(\"\", \"someimage\", \"\").toStringWithQualifier());\n    Assert.assertEquals(\n        \"someotherimage:latest\",\n        ImageReference.of(null, \"library/someotherimage\", null).toStringWithQualifier());\n    Assert.assertEquals(\n        \"someregistry/someotherimage:latest\",\n        ImageReference.of(\"someregistry\", \"someotherimage\", null).toStringWithQualifier());\n    Assert.assertEquals(\n        \"anotherregistry/anotherimage:sometag\",\n        ImageReference.of(\"anotherregistry\", \"anotherimage\", \"sometag\").toStringWithQualifier());\n    Assert.assertEquals(\n        \"anotherregistry/anotherimage@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n        ImageReference.of(\n                \"anotherregistry\",\n                \"anotherimage\",\n                null,\n                \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")\n            .toStringWithQualifier());\n    Assert.assertEquals(\n        \"anotherregistry/anotherimage@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n        ImageReference.of(\n                \"anotherregistry\",\n                \"anotherimage\",\n                \"sometag\",\n                \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")\n            .toStringWithQualifier());\n  }\n\n  @Test\n  public void testIsScratch() throws InvalidImageReferenceException {\n    Assert.assertTrue(ImageReference.parse(\"scratch\").isScratch());\n    Assert.assertTrue(ImageReference.scratch().isScratch());\n    Assert.assertFalse(ImageReference.of(\"\", \"scratch\", \"\").isScratch());\n    Assert.assertFalse(ImageReference.of(null, \"scratch\", null).isScratch());\n  }\n\n  @Test\n  public void testToString_scratch() {\n    Assert.assertEquals(\"scratch\", ImageReference.scratch().toString());\n  }\n\n  @Test\n  public void testGetRegistry() {\n    Assert.assertEquals(\n        \"registry-1.docker.io\", ImageReference.of(null, \"someimage\", null).getRegistry());\n    Assert.assertEquals(\n        \"registry-1.docker.io\", ImageReference.of(\"docker.io\", \"someimage\", null).getRegistry());\n    Assert.assertEquals(\n        \"index.docker.io\", ImageReference.of(\"index.docker.io\", \"someimage\", null).getRegistry());\n    Assert.assertEquals(\n        \"registry.hub.docker.com\",\n        ImageReference.of(\"registry.hub.docker.com\", \"someimage\", null).getRegistry());\n    Assert.assertEquals(\"gcr.io\", ImageReference.of(\"gcr.io\", \"someimage\", null).getRegistry());\n  }\n\n  @Test\n  public void testEquality() throws InvalidImageReferenceException {\n    ImageReference image1 = ImageReference.parse(\"gcr.io/project/image:tag\");\n    ImageReference image2 = ImageReference.parse(\"gcr.io/project/image:tag\");\n\n    Assert.assertEquals(image1, image2);\n    Assert.assertEquals(image1.hashCode(), image2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentRegistry() throws InvalidImageReferenceException {\n    ImageReference image1 = ImageReference.parse(\"gcr.io/project/image:tag\");\n    ImageReference image2 = ImageReference.parse(\"registry-1.docker.io/project/image:tag\");\n\n    Assert.assertNotEquals(image1, image2);\n    Assert.assertNotEquals(image1.hashCode(), image2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentRepository() throws InvalidImageReferenceException {\n    ImageReference image1 = ImageReference.parse(\"gcr.io/project/image:tag\");\n    ImageReference image2 = ImageReference.parse(\"gcr.io/project2/image:tag\");\n\n    Assert.assertNotEquals(image1, image2);\n    Assert.assertNotEquals(image1.hashCode(), image2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentTag() throws InvalidImageReferenceException {\n    ImageReference image1 = ImageReference.parse(\"gcr.io/project/image:tag1\");\n    ImageReference image2 = ImageReference.parse(\"gcr.io/project/image:tag2\");\n\n    Assert.assertNotEquals(image1, image2);\n    Assert.assertNotEquals(image1.hashCode(), image2.hashCode());\n  }\n\n  private void verifyParse(String registry, String repository, String tag, String digest)\n      throws InvalidImageReferenceException {\n    // Gets the expected parsed components.\n    String expectedRegistry = registry;\n    if (Strings.isNullOrEmpty(expectedRegistry)) {\n      expectedRegistry = \"registry-1.docker.io\";\n    }\n    String expectedRepository = repository;\n    if (\"registry-1.docker.io\".equals(expectedRegistry) && repository.indexOf('/') < 0) {\n      expectedRepository = \"library/\" + expectedRepository;\n    }\n    String expectedTag = tag;\n    if (Strings.isNullOrEmpty(expectedTag) && Strings.isNullOrEmpty(digest)) {\n      expectedTag = \"latest\";\n    }\n    if (Strings.isNullOrEmpty(expectedTag)) {\n      expectedTag = null;\n    }\n\n    String expectedDigest = digest;\n    if (Strings.isNullOrEmpty(digest)) {\n      expectedDigest = null;\n    }\n\n    // Builds the image reference to parse.\n    StringBuilder imageReferenceBuilder = new StringBuilder();\n    if (!Strings.isNullOrEmpty(registry)) {\n      imageReferenceBuilder.append(registry).append('/');\n    }\n    imageReferenceBuilder.append(repository);\n    if (!Strings.isNullOrEmpty(tag)) {\n      imageReferenceBuilder.append(':').append(tag);\n    }\n    if (!Strings.isNullOrEmpty(digest)) {\n      imageReferenceBuilder.append('@').append(digest);\n    }\n\n    ImageReference imageReference = ImageReference.parse(imageReferenceBuilder.toString());\n\n    Assert.assertEquals(expectedRegistry, imageReference.getRegistry());\n    Assert.assertEquals(expectedRepository, imageReference.getRepository());\n    Assert.assertEquals(expectedTag, imageReference.getTag().orElse(null));\n    Assert.assertEquals(expectedDigest, imageReference.getDigest().orElse(null));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/JavaContainerBuilderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.RelativeUnixPath;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link JavaContainerBuilder}. */\npublic class JavaContainerBuilderTest {\n\n  /** Gets a resource file as a {@link Path}. */\n  private static Path getResource(String directory) throws URISyntaxException {\n    return Paths.get(Resources.getResource(directory).toURI());\n  }\n\n  /** Gets the extraction paths in the specified layer of a give {@link BuildContext}. */\n  private static List<AbsoluteUnixPath> getExtractionPaths(\n      BuildContext buildContext, String layerName) {\n    return buildContext.getLayerConfigurations().stream()\n        .filter(layerConfiguration -> layerConfiguration.getName().equals(layerName))\n        .findFirst()\n        .map(\n            layerConfiguration ->\n                layerConfiguration.getEntries().stream()\n                    .map(FileEntry::getExtractionPath)\n                    .collect(Collectors.toList()))\n        .orElse(ImmutableList.of());\n  }\n\n  @Test\n  public void testToJibContainerBuilder_all()\n      throws InvalidImageReferenceException, URISyntaxException, IOException,\n          CacheDirectoryCreationException {\n    BuildContext buildContext =\n        JavaContainerBuilder.from(\"scratch\")\n            .setAppRoot(\"/hello\")\n            .addResources(getResource(\"core/application/resources\"))\n            .addClasses(getResource(\"core/application/classes\"))\n            .addDependencies(\n                getResource(\"core/application/dependencies/dependency-1.0.0.jar\"),\n                getResource(\"core/application/dependencies/more/dependency-1.0.0.jar\"))\n            .addSnapshotDependencies(\n                getResource(\"core/application/snapshot-dependencies/dependency-1.0.0-SNAPSHOT.jar\"))\n            .addProjectDependencies(\n                getResource(\"core/application/dependencies/libraryA.jar\"),\n                getResource(\"core/application/dependencies/libraryB.jar\"))\n            .addToClasspath(getResource(\"core/fileA\"), getResource(\"core/fileB\"))\n            .setClassesDestination(RelativeUnixPath.get(\"different-classes\"))\n            .setResourcesDestination(RelativeUnixPath.get(\"different-resources\"))\n            .setDependenciesDestination(RelativeUnixPath.get(\"different-libs\"))\n            .setOthersDestination(RelativeUnixPath.get(\"different-classpath\"))\n            .addJvmFlags(\"-xflag1\", \"-xflag2\")\n            .setMainClass(\"HelloWorld\")\n            .toContainerBuilder()\n            .toBuildContext(Containerizer.to(RegistryImage.named(\"hello\")));\n\n    // Check entrypoint\n    Assert.assertEquals(\n        ImmutableList.of(\n            \"java\",\n            \"-xflag1\",\n            \"-xflag2\",\n            \"-cp\",\n            \"/hello/different-resources:/hello/different-classes:/hello/different-libs/*:/hello/different-classpath\",\n            \"HelloWorld\"),\n        buildContext.getContainerConfiguration().getEntrypoint());\n\n    // Check dependencies\n    List<AbsoluteUnixPath> expectedDependencies =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/hello/different-libs/dependency-1.0.0-770.jar\"),\n            AbsoluteUnixPath.get(\"/hello/different-libs/dependency-1.0.0-200.jar\"));\n    Assert.assertEquals(expectedDependencies, getExtractionPaths(buildContext, \"dependencies\"));\n\n    // Check snapshots\n    List<AbsoluteUnixPath> expectedSnapshotDependencies =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/hello/different-libs/dependency-1.0.0-SNAPSHOT.jar\"));\n    Assert.assertEquals(\n        expectedSnapshotDependencies, getExtractionPaths(buildContext, \"snapshot dependencies\"));\n\n    List<AbsoluteUnixPath> expectedProjectDependencies =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/hello/different-libs/libraryA.jar\"),\n            AbsoluteUnixPath.get(\"/hello/different-libs/libraryB.jar\"));\n    Assert.assertEquals(\n        expectedProjectDependencies, getExtractionPaths(buildContext, \"project dependencies\"));\n\n    // Check resources\n    List<AbsoluteUnixPath> expectedResources =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/hello/different-resources/resourceA\"),\n            AbsoluteUnixPath.get(\"/hello/different-resources/resourceB\"),\n            AbsoluteUnixPath.get(\"/hello/different-resources/world\"));\n    Assert.assertEquals(expectedResources, getExtractionPaths(buildContext, \"resources\"));\n\n    // Check classes\n    List<AbsoluteUnixPath> expectedClasses =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/hello/different-classes/HelloWorld.class\"),\n            AbsoluteUnixPath.get(\"/hello/different-classes/some.class\"));\n    Assert.assertEquals(expectedClasses, getExtractionPaths(buildContext, \"classes\"));\n\n    // Check additional classpath files\n    List<AbsoluteUnixPath> expectedOthers =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/hello/different-classpath/fileA\"),\n            AbsoluteUnixPath.get(\"/hello/different-classpath/fileB\"));\n    Assert.assertEquals(expectedOthers, getExtractionPaths(buildContext, \"extra files\"));\n  }\n\n  @Test\n  public void testToJibContainerBuilder_missingAndMultipleAdds()\n      throws InvalidImageReferenceException, URISyntaxException, IOException,\n          CacheDirectoryCreationException {\n    BuildContext buildContext =\n        JavaContainerBuilder.from(\"scratch\")\n            .addDependencies(getResource(\"core/application/dependencies/libraryA.jar\"))\n            .addDependencies(getResource(\"core/application/dependencies/libraryB.jar\"))\n            .addSnapshotDependencies(\n                getResource(\"core/application/snapshot-dependencies/dependency-1.0.0-SNAPSHOT.jar\"))\n            .addClasses(getResource(\"core/application/classes/\"))\n            .addClasses(getResource(\"core/class-finder-tests/extension\"))\n            .setMainClass(\"HelloWorld\")\n            .toContainerBuilder()\n            .toBuildContext(Containerizer.to(RegistryImage.named(\"hello\")));\n\n    // Check entrypoint\n    Assert.assertEquals(\n        ImmutableList.of(\"java\", \"-cp\", \"/app/libs/*:/app/classes\", \"HelloWorld\"),\n        buildContext.getContainerConfiguration().getEntrypoint());\n\n    // Check dependencies\n    List<AbsoluteUnixPath> expectedDependencies =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/app/libs/libraryA.jar\"),\n            AbsoluteUnixPath.get(\"/app/libs/libraryB.jar\"));\n    Assert.assertEquals(expectedDependencies, getExtractionPaths(buildContext, \"dependencies\"));\n\n    // Check snapshots\n    List<AbsoluteUnixPath> expectedSnapshotDependencies =\n        ImmutableList.of(AbsoluteUnixPath.get(\"/app/libs/dependency-1.0.0-SNAPSHOT.jar\"));\n    Assert.assertEquals(\n        expectedSnapshotDependencies, getExtractionPaths(buildContext, \"snapshot dependencies\"));\n\n    // Check classes\n    List<AbsoluteUnixPath> expectedClasses =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/app/classes/HelloWorld.class\"),\n            AbsoluteUnixPath.get(\"/app/classes/some.class\"),\n            AbsoluteUnixPath.get(\"/app/classes/main/\"),\n            AbsoluteUnixPath.get(\"/app/classes/main/MainClass.class\"),\n            AbsoluteUnixPath.get(\"/app/classes/pack/\"),\n            AbsoluteUnixPath.get(\"/app/classes/pack/Apple.class\"),\n            AbsoluteUnixPath.get(\"/app/classes/pack/Orange.class\"));\n    Assert.assertEquals(expectedClasses, getExtractionPaths(buildContext, \"classes\"));\n\n    // Check empty layers\n    Assert.assertEquals(ImmutableList.of(), getExtractionPaths(buildContext, \"resources\"));\n    Assert.assertEquals(ImmutableList.of(), getExtractionPaths(buildContext, \"extra files\"));\n  }\n\n  @Test\n  public void testToJibContainerBuilder_setAppRootLate()\n      throws URISyntaxException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    BuildContext buildContext =\n        JavaContainerBuilder.from(\"scratch\")\n            .addClasses(getResource(\"core/application/classes\"))\n            .addResources(getResource(\"core/application/resources\"))\n            .addDependencies(getResource(\"core/application/dependencies/libraryA.jar\"))\n            .addToClasspath(getResource(\"core/fileA\"))\n            .setAppRoot(\"/different\")\n            .setMainClass(\"HelloWorld\")\n            .toContainerBuilder()\n            .toBuildContext(Containerizer.to(RegistryImage.named(\"hello\")));\n\n    // Check entrypoint\n    Assert.assertEquals(\n        ImmutableList.of(\n            \"java\",\n            \"-cp\",\n            \"/different/classes:/different/resources:/different/libs/*:/different/classpath\",\n            \"HelloWorld\"),\n        buildContext.getContainerConfiguration().getEntrypoint());\n\n    // Check classes\n    List<AbsoluteUnixPath> expectedClasses =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/different/classes/HelloWorld.class\"),\n            AbsoluteUnixPath.get(\"/different/classes/some.class\"));\n    Assert.assertEquals(expectedClasses, getExtractionPaths(buildContext, \"classes\"));\n\n    // Check resources\n    List<AbsoluteUnixPath> expectedResources =\n        ImmutableList.of(\n            AbsoluteUnixPath.get(\"/different/resources/resourceA\"),\n            AbsoluteUnixPath.get(\"/different/resources/resourceB\"),\n            AbsoluteUnixPath.get(\"/different/resources/world\"));\n    Assert.assertEquals(expectedResources, getExtractionPaths(buildContext, \"resources\"));\n\n    // Check dependencies\n    List<AbsoluteUnixPath> expectedDependencies =\n        ImmutableList.of(AbsoluteUnixPath.get(\"/different/libs/libraryA.jar\"));\n    Assert.assertEquals(expectedDependencies, getExtractionPaths(buildContext, \"dependencies\"));\n\n    Assert.assertEquals(expectedClasses, getExtractionPaths(buildContext, \"classes\"));\n\n    // Check additional classpath files\n    List<AbsoluteUnixPath> expectedOthers =\n        ImmutableList.of(AbsoluteUnixPath.get(\"/different/classpath/fileA\"));\n    Assert.assertEquals(expectedOthers, getExtractionPaths(buildContext, \"extra files\"));\n  }\n\n  @Test\n  public void testToJibContainerBuilder_mainClassNull()\n      throws IOException, InvalidImageReferenceException, CacheDirectoryCreationException,\n          URISyntaxException {\n    BuildContext buildContext =\n        JavaContainerBuilder.from(\"scratch\")\n            .addClasses(getResource(\"core/application/classes/\"))\n            .toContainerBuilder()\n            .toBuildContext(Containerizer.to(RegistryImage.named(\"hello\")));\n    Assert.assertNull(buildContext.getContainerConfiguration().getEntrypoint());\n\n    try {\n      JavaContainerBuilder.from(\"scratch\").addJvmFlags(\"-flag1\", \"-flag2\").toContainerBuilder();\n      Assert.fail();\n\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\n          \"Failed to construct entrypoint on JavaContainerBuilder; jvmFlags were set, but \"\n              + \"mainClass is null. Specify the main class using \"\n              + \"JavaContainerBuilder#setMainClass(String), or consider using MainClassFinder to \"\n              + \"infer the main class.\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testToJibContainerBuilder_classpathEmpty()\n      throws IOException, InvalidImageReferenceException {\n    try {\n      JavaContainerBuilder.from(\"scratch\").setMainClass(\"Hello\").toContainerBuilder();\n      Assert.fail();\n\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\n          \"Failed to construct entrypoint because no files were added to the JavaContainerBuilder\",\n          ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerBuilderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.Spy;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link JibContainerBuilder}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class JibContainerBuilderTest {\n\n  @Spy private BuildContext.Builder spyBuildContextBuilder;\n  @Mock private FileEntriesLayer mockLayerConfiguration1;\n  @Mock private FileEntriesLayer mockLayerConfiguration2;\n  @Mock private CredentialRetriever mockCredentialRetriever;\n  @Mock private Consumer<JibEvent> mockJibEventConsumer;\n  @Mock private JibEvent mockJibEvent;\n\n  @Test\n  public void testToBuildContext_containerConfigurationSet()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException {\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(ImageReference.parse(\"base/image\")).build();\n    JibContainerBuilder jibContainerBuilder =\n        new JibContainerBuilder(imageConfiguration, spyBuildContextBuilder)\n            .setPlatforms(ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\")))\n            .setEntrypoint(Arrays.asList(\"entry\", \"point\"))\n            .setEnvironment(ImmutableMap.of(\"name\", \"value\"))\n            .setExposedPorts(ImmutableSet.of(Port.tcp(1234), Port.udp(5678)))\n            .setLabels(ImmutableMap.of(\"key\", \"value\"))\n            .setProgramArguments(Arrays.asList(\"program\", \"arguments\"))\n            .setCreationTime(Instant.ofEpochMilli(1000))\n            .setUser(\"user\")\n            .setWorkingDirectory(AbsoluteUnixPath.get(\"/working/directory\"));\n\n    BuildContext buildContext =\n        jibContainerBuilder.toBuildContext(Containerizer.to(RegistryImage.named(\"target/image\")));\n    ContainerConfiguration containerConfiguration = buildContext.getContainerConfiguration();\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\")),\n        containerConfiguration.getPlatforms());\n    Assert.assertEquals(Arrays.asList(\"entry\", \"point\"), containerConfiguration.getEntrypoint());\n    Assert.assertEquals(\n        ImmutableMap.of(\"name\", \"value\"), containerConfiguration.getEnvironmentMap());\n    Assert.assertEquals(\n        ImmutableSet.of(Port.tcp(1234), Port.udp(5678)), containerConfiguration.getExposedPorts());\n    Assert.assertEquals(ImmutableMap.of(\"key\", \"value\"), containerConfiguration.getLabels());\n    Assert.assertEquals(\n        Arrays.asList(\"program\", \"arguments\"), containerConfiguration.getProgramArguments());\n    Assert.assertEquals(Instant.ofEpochMilli(1000), containerConfiguration.getCreationTime());\n    Assert.assertEquals(\"user\", containerConfiguration.getUser());\n    Assert.assertEquals(\n        AbsoluteUnixPath.get(\"/working/directory\"), containerConfiguration.getWorkingDirectory());\n  }\n\n  @Test\n  public void testToBuildContext_containerConfigurationAdd()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException {\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(ImageReference.parse(\"base/image\")).build();\n    JibContainerBuilder jibContainerBuilder =\n        new JibContainerBuilder(imageConfiguration, spyBuildContextBuilder)\n            .addPlatform(\"testArchitecture\", \"testOS\")\n            .setEntrypoint(\"entry\", \"point\")\n            .setEnvironment(ImmutableMap.of(\"name\", \"value\"))\n            .addEnvironmentVariable(\"environment\", \"variable\")\n            .setExposedPorts(Port.tcp(1234), Port.udp(5678))\n            .addExposedPort(Port.tcp(1337))\n            .setLabels(ImmutableMap.of(\"key\", \"value\"))\n            .addLabel(\"added\", \"label\")\n            .setProgramArguments(\"program\", \"arguments\");\n\n    BuildContext buildContext =\n        jibContainerBuilder.toBuildContext(Containerizer.to(RegistryImage.named(\"target/image\")));\n    ContainerConfiguration containerConfiguration = buildContext.getContainerConfiguration();\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\"), new Platform(\"amd64\", \"linux\")),\n        containerConfiguration.getPlatforms());\n    Assert.assertEquals(Arrays.asList(\"entry\", \"point\"), containerConfiguration.getEntrypoint());\n    Assert.assertEquals(\n        ImmutableMap.of(\"name\", \"value\", \"environment\", \"variable\"),\n        containerConfiguration.getEnvironmentMap());\n    Assert.assertEquals(\n        ImmutableSet.of(Port.tcp(1234), Port.udp(5678), Port.tcp(1337)),\n        containerConfiguration.getExposedPorts());\n    Assert.assertEquals(\n        ImmutableMap.of(\"key\", \"value\", \"added\", \"label\"), containerConfiguration.getLabels());\n    Assert.assertEquals(\n        Arrays.asList(\"program\", \"arguments\"), containerConfiguration.getProgramArguments());\n    Assert.assertEquals(Instant.EPOCH, containerConfiguration.getCreationTime());\n  }\n\n  @Test\n  public void testToBuildContext()\n      throws InvalidImageReferenceException, CredentialRetrievalException,\n          CacheDirectoryCreationException {\n    ExecutorService executorService = MoreExecutors.newDirectExecutorService();\n    RegistryImage targetImage =\n        RegistryImage.named(ImageReference.of(\"gcr.io\", \"my-project/my-app\", null))\n            .addCredential(\"username\", \"password\");\n    Containerizer containerizer =\n        Containerizer.to(targetImage)\n            .setBaseImageLayersCache(Paths.get(\"base/image/layers\"))\n            .setApplicationLayersCache(Paths.get(\"application/layers\"))\n            .setExecutorService(executorService)\n            .addEventHandler(mockJibEventConsumer)\n            .setAlwaysCacheBaseImage(false);\n\n    ImageConfiguration baseImageConfiguration =\n        ImageConfiguration.builder(ImageReference.parse(\"base/image\"))\n            .setCredentialRetrievers(Collections.singletonList(mockCredentialRetriever))\n            .build();\n    JibContainerBuilder jibContainerBuilder =\n        new JibContainerBuilder(baseImageConfiguration, spyBuildContextBuilder)\n            .setFileEntriesLayers(Arrays.asList(mockLayerConfiguration1, mockLayerConfiguration2));\n    BuildContext buildContext = jibContainerBuilder.toBuildContext(containerizer);\n\n    Assert.assertEquals(\n        spyBuildContextBuilder.build().getContainerConfiguration(),\n        buildContext.getContainerConfiguration());\n\n    Assert.assertEquals(\n        \"base/image\", buildContext.getBaseImageConfiguration().getImage().toString());\n    Assert.assertEquals(\n        Arrays.asList(mockCredentialRetriever),\n        buildContext.getBaseImageConfiguration().getCredentialRetrievers());\n\n    Assert.assertEquals(\n        \"gcr.io/my-project/my-app\",\n        buildContext.getTargetImageConfiguration().getImage().toString());\n    Assert.assertEquals(\n        1, buildContext.getTargetImageConfiguration().getCredentialRetrievers().size());\n    Assert.assertEquals(\n        Credential.from(\"username\", \"password\"),\n        buildContext\n            .getTargetImageConfiguration()\n            .getCredentialRetrievers()\n            .get(0)\n            .retrieve()\n            .orElseThrow(AssertionError::new));\n\n    Assert.assertEquals(ImmutableSet.of(\"latest\"), buildContext.getAllTargetImageTags());\n\n    Mockito.verify(spyBuildContextBuilder)\n        .setBaseImageLayersCacheDirectory(Paths.get(\"base/image/layers\"));\n    Mockito.verify(spyBuildContextBuilder)\n        .setApplicationLayersCacheDirectory(Paths.get(\"application/layers\"));\n\n    Assert.assertEquals(\n        Arrays.asList(mockLayerConfiguration1, mockLayerConfiguration2),\n        buildContext.getLayerConfigurations());\n\n    Assert.assertSame(executorService, buildContext.getExecutorService());\n\n    buildContext.getEventHandlers().dispatch(mockJibEvent);\n    Mockito.verify(mockJibEventConsumer).accept(mockJibEvent);\n\n    Assert.assertEquals(\"jib-core\", buildContext.getToolName());\n\n    Assert.assertSame(V22ManifestTemplate.class, buildContext.getTargetFormat());\n\n    Assert.assertEquals(\"jib-core\", buildContext.getToolName());\n\n    // Changes jibContainerBuilder.\n    buildContext =\n        jibContainerBuilder\n            .setFormat(ImageFormat.OCI)\n            .toBuildContext(\n                containerizer\n                    .withAdditionalTag(\"tag1\")\n                    .withAdditionalTag(\"tag2\")\n                    .setToolName(\"toolName\"));\n    Assert.assertSame(OciManifestTemplate.class, buildContext.getTargetFormat());\n    Assert.assertEquals(\n        ImmutableSet.of(\"latest\", \"tag1\", \"tag2\"), buildContext.getAllTargetImageTags());\n    Assert.assertEquals(\"toolName\", buildContext.getToolName());\n    Assert.assertFalse(buildContext.getAlwaysCacheBaseImage());\n  }\n\n  @Test\n  public void testToContainerBuildPlan_default() throws InvalidImageReferenceException {\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(ImageReference.parse(\"base/image\")).build();\n    JibContainerBuilder containerBuilder =\n        new JibContainerBuilder(imageConfiguration, spyBuildContextBuilder);\n\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(\"base/image\", buildPlan.getBaseImage());\n    Assert.assertEquals(ImmutableSet.of(new Platform(\"amd64\", \"linux\")), buildPlan.getPlatforms());\n    Assert.assertEquals(Instant.EPOCH, buildPlan.getCreationTime());\n    Assert.assertEquals(ImageFormat.Docker, buildPlan.getFormat());\n    Assert.assertEquals(Collections.emptyMap(), buildPlan.getEnvironment());\n    Assert.assertEquals(Collections.emptyMap(), buildPlan.getLabels());\n    Assert.assertEquals(Collections.emptySet(), buildPlan.getVolumes());\n    Assert.assertEquals(Collections.emptySet(), buildPlan.getExposedPorts());\n    Assert.assertNull(buildPlan.getUser());\n    Assert.assertNull(buildPlan.getWorkingDirectory());\n    Assert.assertNull(buildPlan.getEntrypoint());\n    Assert.assertNull(buildPlan.getCmd());\n    Assert.assertEquals(Collections.emptyList(), buildPlan.getLayers());\n  }\n\n  @Test\n  public void testToContainerBuildPlan() throws InvalidImageReferenceException, IOException {\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(ImageReference.parse(\"base/image\")).build();\n    JibContainerBuilder containerBuilder =\n        new JibContainerBuilder(imageConfiguration, spyBuildContextBuilder)\n            .setPlatforms(ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\")))\n            .setCreationTime(Instant.ofEpochMilli(1000))\n            .setFormat(ImageFormat.OCI)\n            .setEnvironment(ImmutableMap.of(\"env\", \"var\"))\n            .setLabels(ImmutableMap.of(\"com.example.label\", \"value\"))\n            .setVolumes(AbsoluteUnixPath.get(\"/mnt/vol\"), AbsoluteUnixPath.get(\"/media/data\"))\n            .setExposedPorts(ImmutableSet.of(Port.tcp(1234), Port.udp(5678)))\n            .setUser(\"user\")\n            .setWorkingDirectory(AbsoluteUnixPath.get(\"/working/directory\"))\n            .setEntrypoint(Arrays.asList(\"entry\", \"point\"))\n            .setProgramArguments(Arrays.asList(\"program\", \"arguments\"))\n            .addLayer(Arrays.asList(Paths.get(\"/non/existing/foo\")), \"/into/this\");\n\n    ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(\"base/image\", buildPlan.getBaseImage());\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\")), buildPlan.getPlatforms());\n    Assert.assertEquals(Instant.ofEpochMilli(1000), buildPlan.getCreationTime());\n    Assert.assertEquals(ImageFormat.OCI, buildPlan.getFormat());\n    Assert.assertEquals(ImmutableMap.of(\"env\", \"var\"), buildPlan.getEnvironment());\n    Assert.assertEquals(ImmutableMap.of(\"com.example.label\", \"value\"), buildPlan.getLabels());\n    Assert.assertEquals(\n        ImmutableSet.of(AbsoluteUnixPath.get(\"/mnt/vol\"), AbsoluteUnixPath.get(\"/media/data\")),\n        buildPlan.getVolumes());\n    Assert.assertEquals(\n        ImmutableSet.of(Port.tcp(1234), Port.udp(5678)), buildPlan.getExposedPorts());\n    Assert.assertEquals(\"user\", buildPlan.getUser());\n    Assert.assertEquals(\n        AbsoluteUnixPath.get(\"/working/directory\"), buildPlan.getWorkingDirectory());\n    Assert.assertEquals(Arrays.asList(\"entry\", \"point\"), buildPlan.getEntrypoint());\n    Assert.assertEquals(Arrays.asList(\"program\", \"arguments\"), buildPlan.getCmd());\n\n    Assert.assertEquals(1, buildPlan.getLayers().size());\n    MatcherAssert.assertThat(\n        buildPlan.getLayers().get(0), CoreMatchers.instanceOf(FileEntriesLayer.class));\n    Assert.assertEquals(\n        Arrays.asList(\n            new FileEntry(\n                Paths.get(\"/non/existing/foo\"),\n                AbsoluteUnixPath.get(\"/into/this/foo\"),\n                FilePermissions.fromOctalString(\"644\"),\n                Instant.ofEpochSecond(1))),\n        ((FileEntriesLayer) buildPlan.getLayers().get(0)).getEntries());\n  }\n\n  @Test\n  public void setApplyContainerBuildPlan()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException {\n    FileEntriesLayer layer =\n        FileEntriesLayer.builder()\n            .addEntry(Paths.get(\"/src/file/foo\"), AbsoluteUnixPath.get(\"/path/in/container\"))\n            .build();\n    ContainerBuildPlan buildPlan =\n        ContainerBuildPlan.builder()\n            .setBaseImage(\"some/base\")\n            .setPlatforms(ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\")))\n            .setFormat(ImageFormat.OCI)\n            .setCreationTime(Instant.ofEpochMilli(30))\n            .setEnvironment(ImmutableMap.of(\"env\", \"var\"))\n            .setVolumes(\n                ImmutableSet.of(AbsoluteUnixPath.get(\"/mnt/foo\"), AbsoluteUnixPath.get(\"/bar\")))\n            .setLabels(ImmutableMap.of(\"com.example.label\", \"cool\"))\n            .setExposedPorts(ImmutableSet.of(Port.tcp(443)))\n            .setLayers(Arrays.asList(layer))\n            .setUser(\":\")\n            .setWorkingDirectory(AbsoluteUnixPath.get(\"/workspace\"))\n            .setEntrypoint(Arrays.asList(\"foo\", \"entrypoint\"))\n            .setCmd(Arrays.asList(\"bar\", \"cmd\"))\n            .build();\n\n    ImageConfiguration imageConfiguration =\n        ImageConfiguration.builder(ImageReference.parse(\"initial/base\")).build();\n    JibContainerBuilder containerBuilder =\n        new JibContainerBuilder(imageConfiguration, spyBuildContextBuilder)\n            .applyContainerBuildPlan(buildPlan);\n\n    BuildContext buildContext =\n        containerBuilder.toBuildContext(Containerizer.to(RegistryImage.named(\"target/image\")));\n    Assert.assertEquals(\n        \"some/base\", buildContext.getBaseImageConfiguration().getImage().toString());\n    Assert.assertEquals(OciManifestTemplate.class, buildContext.getTargetFormat());\n    Assert.assertEquals(1, buildContext.getLayerConfigurations().size());\n    Assert.assertEquals(1, buildContext.getLayerConfigurations().get(0).getEntries().size());\n    Assert.assertEquals(\n        Arrays.asList(\n            new FileEntry(\n                Paths.get(\"/src/file/foo\"),\n                AbsoluteUnixPath.get(\"/path/in/container\"),\n                FilePermissions.fromOctalString(\"644\"),\n                Instant.ofEpochSecond(1))),\n        buildContext.getLayerConfigurations().get(0).getEntries());\n\n    ContainerConfiguration containerConfiguration = buildContext.getContainerConfiguration();\n    Assert.assertEquals(Instant.ofEpochMilli(30), containerConfiguration.getCreationTime());\n    Assert.assertEquals(ImmutableMap.of(\"env\", \"var\"), containerConfiguration.getEnvironmentMap());\n    Assert.assertEquals(\n        ImmutableMap.of(\"com.example.label\", \"cool\"), containerConfiguration.getLabels());\n    Assert.assertEquals(\n        ImmutableSet.of(AbsoluteUnixPath.get(\"/mnt/foo\"), AbsoluteUnixPath.get(\"/bar\")),\n        containerConfiguration.getVolumes());\n    Assert.assertEquals(ImmutableSet.of(Port.tcp(443)), containerConfiguration.getExposedPorts());\n    Assert.assertEquals(\":\", containerConfiguration.getUser());\n    Assert.assertEquals(\n        AbsoluteUnixPath.get(\"/workspace\"), containerConfiguration.getWorkingDirectory());\n    Assert.assertEquals(Arrays.asList(\"foo\", \"entrypoint\"), containerConfiguration.getEntrypoint());\n    Assert.assertEquals(Arrays.asList(\"bar\", \"cmd\"), containerConfiguration.getProgramArguments());\n\n    ContainerBuildPlan convertedPlan = containerBuilder.toContainerBuildPlan();\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"testArchitecture\", \"testOS\")), convertedPlan.getPlatforms());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.builder.steps.BuildResult;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.common.collect.ImmutableSet;\nimport java.security.DigestException;\nimport java.util.Set;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.mockito.Mockito;\n\n/** Tests for {@link JibContainer}. */\npublic class JibContainerTest {\n\n  @Rule public TemporaryFolder temporaryDirectory = new TemporaryFolder();\n\n  private ImageReference targetImage1;\n  private ImageReference targetImage2;\n  private DescriptorDigest digest1;\n  private DescriptorDigest digest2;\n  private Set<String> tags1;\n  private Set<String> tags2;\n\n  @Before\n  public void setUp() throws DigestException, InvalidImageReferenceException {\n    targetImage1 = ImageReference.parse(\"gcr.io/project/image:tag\");\n    targetImage2 = ImageReference.parse(\"gcr.io/project/image:tag2\");\n    digest1 =\n        DescriptorDigest.fromDigest(\n            \"sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789\");\n    digest2 =\n        DescriptorDigest.fromDigest(\n            \"sha256:9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba\");\n    tags1 = ImmutableSet.of(\"latest\", \"custom-tag\");\n    tags2 = ImmutableSet.of(\"latest\");\n  }\n\n  @Test\n  public void testCreation() {\n    JibContainer container = new JibContainer(targetImage1, digest1, digest2, tags1, true);\n\n    Assert.assertEquals(targetImage1, container.getTargetImage());\n    Assert.assertEquals(digest1, container.getDigest());\n    Assert.assertEquals(digest2, container.getImageId());\n    Assert.assertEquals(tags1, container.getTags());\n    Assert.assertTrue(container.isImagePushed());\n  }\n\n  @Test\n  public void testEquality() {\n    JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1, true);\n    JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1, true);\n\n    Assert.assertEquals(container1, container2);\n    Assert.assertEquals(container1.hashCode(), container2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentTargetImage() {\n    JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1, true);\n    JibContainer container2 = new JibContainer(targetImage2, digest1, digest2, tags1, true);\n\n    Assert.assertNotEquals(container1, container2);\n    Assert.assertNotEquals(container1.hashCode(), container2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentImageDigest() {\n    JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1, true);\n    JibContainer container2 = new JibContainer(targetImage1, digest2, digest2, tags1, true);\n\n    Assert.assertNotEquals(container1, container2);\n    Assert.assertNotEquals(container1.hashCode(), container2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentImageId() {\n    JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1, true);\n    JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1, true);\n\n    Assert.assertNotEquals(container1, container2);\n    Assert.assertNotEquals(container1.hashCode(), container2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentTags() {\n    JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1, true);\n    JibContainer container2 = new JibContainer(targetImage1, digest1, digest1, tags2, true);\n\n    Assert.assertNotEquals(container1, container2);\n    Assert.assertNotEquals(container1.hashCode(), container2.hashCode());\n  }\n\n  @Test\n  public void testEquality_differentImagePushed() {\n    JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1, true);\n    JibContainer container2 = new JibContainer(targetImage1, digest1, digest1, tags1, false);\n\n    Assert.assertNotEquals(container1, container2);\n    Assert.assertNotEquals(container1.hashCode(), container2.hashCode());\n  }\n\n  @Test\n  public void testCreation_withBuildContextAndBuildResult() {\n    BuildResult buildResult = Mockito.mock(BuildResult.class);\n    BuildContext buildContext = Mockito.mock(BuildContext.class);\n    ImageConfiguration mockTargetConfiguration = Mockito.mock(ImageConfiguration.class);\n\n    when(buildResult.getImageDigest()).thenReturn(digest1);\n    when(buildResult.getImageId()).thenReturn(digest1);\n    when(buildResult.isImagePushed()).thenReturn(true);\n    when(mockTargetConfiguration.getImage()).thenReturn(targetImage1);\n    when(buildContext.getTargetImageConfiguration()).thenReturn(mockTargetConfiguration);\n    when(buildContext.getAllTargetImageTags()).thenReturn(ImmutableSet.copyOf(tags1));\n\n    JibContainer container = JibContainer.from(buildContext, buildResult);\n    Assert.assertEquals(targetImage1, container.getTargetImage());\n    Assert.assertEquals(digest1, container.getDigest());\n    Assert.assertEquals(digest1, container.getImageId());\n    Assert.assertEquals(tags1, container.getTags());\n    Assert.assertTrue(container.isImagePushed());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/MainClassFinderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.MainClassFinder.Result;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.function.Consumer;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link MainClassFinder}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class MainClassFinderTest {\n\n  @Mock private Consumer<LogEvent> logEventConsumer;\n\n  @Test\n  public void testFindMainClass_simple() throws URISyntaxException, IOException {\n    Path rootDirectory = Paths.get(Resources.getResource(\"core/class-finder-tests/simple\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"HelloWorld\"));\n  }\n\n  @Test\n  public void testFindMainClass_subdirectories() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/subdirectories\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(),\n        CoreMatchers.containsString(\"multi.layered.HelloWorld\"));\n  }\n\n  @Test\n  public void testFindMainClass_noClass() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/no-main\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertEquals(Result.Type.MAIN_CLASS_NOT_FOUND, mainClassFinderResult.getType());\n  }\n\n  @Test\n  public void testFindMainClass_multiple() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/multiple\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertEquals(Result.Type.MULTIPLE_MAIN_CLASSES, mainClassFinderResult.getType());\n    Assert.assertEquals(2, mainClassFinderResult.getFoundMainClasses().size());\n    Assert.assertTrue(\n        mainClassFinderResult.getFoundMainClasses().contains(\"multi.layered.HelloMoon\"));\n    Assert.assertTrue(mainClassFinderResult.getFoundMainClasses().contains(\"HelloWorld\"));\n  }\n\n  @Test\n  public void testFindMainClass_extension() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/extension\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"main.MainClass\"));\n  }\n\n  @Test\n  public void testFindMainClass_importedMethods() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/imported-methods\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"main.MainClass\"));\n  }\n\n  @Test\n  public void testFindMainClass_externalClasses() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/external-classes\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"main.MainClass\"));\n  }\n\n  @Test\n  public void testFindMainClass_innerClasses() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/inner-classes\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(),\n        CoreMatchers.containsString(\"HelloWorld$InnerClass\"));\n  }\n\n  @Test\n  public void testMainClass_varargs() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/varargs\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"HelloWorld\"));\n  }\n\n  @Test\n  public void testMainClass_synthetic() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/synthetic\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"HelloWorldKt\"));\n  }\n\n  @Test\n  public void testMainClass_java25StaticNoArgs() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    Path classFile = rootDirectory.resolve(\"StaticMainNoArgs.class\");\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(java.util.Collections.singletonList(classFile), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"StaticMainNoArgs\"));\n  }\n\n  @Test\n  public void testMainClass_java25InstanceWithArgs() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    Path classFile = rootDirectory.resolve(\"InstanceMainWithArgs.class\");\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(java.util.Collections.singletonList(classFile), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(),\n        CoreMatchers.containsString(\"InstanceMainWithArgs\"));\n  }\n\n  @Test\n  public void testMainClass_java25InstanceNoArgs() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    Path classFile = rootDirectory.resolve(\"InstanceMainNoArgs.class\");\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(java.util.Collections.singletonList(classFile), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(),\n        CoreMatchers.containsString(\"InstanceMainNoArgs\"));\n  }\n\n  @Test\n  public void testMainClass_java25ProtectedMain() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    Path classFile = rootDirectory.resolve(\"ProtectedMain.class\");\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(java.util.Collections.singletonList(classFile), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(), CoreMatchers.containsString(\"ProtectedMain\"));\n  }\n\n  @Test\n  public void testMainClass_java25PackagePrivateMain() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    Path classFile = rootDirectory.resolve(\"PackagePrivateMain.class\");\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(java.util.Collections.singletonList(classFile), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_FOUND, mainClassFinderResult.getType());\n    MatcherAssert.assertThat(\n        mainClassFinderResult.getFoundMainClass(),\n        CoreMatchers.containsString(\"PackagePrivateMain\"));\n  }\n\n  @Test\n  public void testMainClass_java25MultipleFlexibleMains() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(new DirectoryWalker(rootDirectory).walk(), logEventConsumer);\n    Assert.assertEquals(Result.Type.MULTIPLE_MAIN_CLASSES, mainClassFinderResult.getType());\n    Assert.assertEquals(5, mainClassFinderResult.getFoundMainClasses().size());\n    Assert.assertTrue(mainClassFinderResult.getFoundMainClasses().contains(\"StaticMainNoArgs\"));\n    Assert.assertTrue(mainClassFinderResult.getFoundMainClasses().contains(\"InstanceMainWithArgs\"));\n    Assert.assertTrue(mainClassFinderResult.getFoundMainClasses().contains(\"InstanceMainNoArgs\"));\n    Assert.assertTrue(mainClassFinderResult.getFoundMainClasses().contains(\"ProtectedMain\"));\n    Assert.assertTrue(mainClassFinderResult.getFoundMainClasses().contains(\"PackagePrivateMain\"));\n  }\n\n  @Test\n  public void testMainClass_java25PrivateMainNotAllowed() throws URISyntaxException, IOException {\n    Path rootDirectory =\n        Paths.get(Resources.getResource(\"core/class-finder-tests/java25-flexible-main\").toURI());\n    Path classFile = rootDirectory.resolve(\"PrivateMain.class\");\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(java.util.Collections.singletonList(classFile), logEventConsumer);\n    Assert.assertSame(\n        MainClassFinder.Result.Type.MAIN_CLASS_NOT_FOUND, mainClassFinderResult.getType());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/PortsTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.common.collect.ImmutableSet;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Ports}. */\npublic class PortsTest {\n\n  @Test\n  public void testParse() {\n    List<String> goodInputs =\n        Arrays.asList(\"1000\", \"2000-2003\", \"3000-3000\", \"4000/tcp\", \"5000/udp\", \"6000-6002/udp\");\n    ImmutableSet<Port> expected =\n        new ImmutableSet.Builder<Port>()\n            .add(\n                Port.tcp(1000),\n                Port.tcp(2000),\n                Port.tcp(2001),\n                Port.tcp(2002),\n                Port.tcp(2003),\n                Port.tcp(3000),\n                Port.tcp(4000),\n                Port.udp(5000),\n                Port.udp(6000),\n                Port.udp(6001),\n                Port.udp(6002))\n            .build();\n    Set<Port> result = Ports.parse(goodInputs);\n    Assert.assertEquals(expected, result);\n\n    List<String> badInputs = Arrays.asList(\"abc\", \"/udp\", \"1000/abc\", \"a100/tcp\", \"20/udpabc\");\n    for (String input : badInputs) {\n      try {\n        Ports.parse(Collections.singletonList(input));\n        Assert.fail();\n      } catch (NumberFormatException ex) {\n        Assert.assertEquals(\n            \"Invalid port configuration: '\"\n                + input\n                + \"'. Make sure the port is a single number or a range of two numbers separated \"\n                + \"with a '-', with or without protocol specified (e.g. '<portNum>/tcp' or \"\n                + \"'<portNum>/udp').\",\n            ex.getMessage());\n      }\n    }\n\n    try {\n      Ports.parse(Collections.singletonList(\"4002-4000\"));\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\n          \"Invalid port range '4002-4000'; smaller number must come first.\", ex.getMessage());\n    }\n\n    badInputs = Arrays.asList(\"0\", \"70000\", \"0-400\", \"1-70000\");\n    for (String input : badInputs) {\n      try {\n        Ports.parse(Collections.singletonList(input));\n        Assert.fail();\n      } catch (NumberFormatException ex) {\n        Assert.assertEquals(\n            \"Port number '\" + input + \"' is out of usual range (1-65535).\", ex.getMessage());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/RegistryImageTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link RegistryImage}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class RegistryImageTest {\n\n  @Mock private CredentialRetriever mockCredentialRetriever;\n\n  @Test\n  public void testGetters_default() throws InvalidImageReferenceException {\n    RegistryImage image = RegistryImage.named(\"registry/image\");\n\n    Assert.assertEquals(\"registry/image\", image.getImageReference().toString());\n    Assert.assertEquals(0, image.getCredentialRetrievers().size());\n  }\n\n  @Test\n  public void testGetters()\n      throws InvalidImageReferenceException, AssertionError, CredentialRetrievalException {\n    RegistryImage image =\n        RegistryImage.named(\"registry/image\")\n            .addCredentialRetriever(mockCredentialRetriever)\n            .addCredential(\"username\", \"password\");\n\n    Assert.assertEquals(2, image.getCredentialRetrievers().size());\n    Assert.assertSame(mockCredentialRetriever, image.getCredentialRetrievers().get(0));\n    Assert.assertEquals(\n        Credential.from(\"username\", \"password\"),\n        image.getCredentialRetrievers().get(1).retrieve().get());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/api/TarImageTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link TarImage}. */\npublic class TarImageTest {\n\n  @Test\n  public void testGetters_bothSet() throws InvalidImageReferenceException {\n    TarImage tarImage = TarImage.at(Paths.get(\"output/file\")).named(\"tar/image\");\n    Assert.assertEquals(\"tar/image\", tarImage.getImageReference().get().toString());\n    Assert.assertEquals(Paths.get(\"output/file\"), tarImage.getPath());\n  }\n\n  @Test\n  public void testGetters_nameMissing() {\n    TarImage tarImage = TarImage.at(Paths.get(\"output/file\"));\n    Assert.assertFalse(tarImage.getImageReference().isPresent());\n    Assert.assertEquals(Paths.get(\"output/file\"), tarImage.getPath());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/blob/BlobTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.blob;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.hash.WritableContents;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Blob}. */\npublic class BlobTest {\n\n  @Test\n  public void testFromInputStream() throws IOException {\n    String expected = \"crepecake\";\n    InputStream inputStream = new ByteArrayInputStream(expected.getBytes(StandardCharsets.UTF_8));\n    verifyBlobWriteTo(expected, Blobs.from(inputStream));\n  }\n\n  @Test\n  public void testFromFile() throws IOException, URISyntaxException {\n    Path fileA = Paths.get(Resources.getResource(\"core/fileA\").toURI());\n    String expected = new String(Files.readAllBytes(fileA), StandardCharsets.UTF_8);\n    verifyBlobWriteTo(expected, Blobs.from(fileA));\n  }\n\n  @Test\n  public void testFromString() throws IOException {\n    String expected = \"crepecake\";\n    verifyBlobWriteTo(expected, Blobs.from(expected));\n  }\n\n  @Test\n  public void testFromWritableContents() throws IOException {\n    String expected = \"crepecake\";\n\n    WritableContents writableContents =\n        outputStream -> outputStream.write(expected.getBytes(StandardCharsets.UTF_8));\n\n    verifyBlobWriteTo(expected, Blobs.from(writableContents, false));\n  }\n\n  /** Checks that the {@link Blob} streams the expected string. */\n  private void verifyBlobWriteTo(String expected, Blob blob) throws IOException {\n    OutputStream outputStream = new ByteArrayOutputStream();\n    BlobDescriptor blobDescriptor = blob.writeTo(outputStream);\n\n    String output = outputStream.toString();\n    Assert.assertEquals(expected, output);\n\n    byte[] expectedBytes = expected.getBytes(StandardCharsets.UTF_8);\n    Assert.assertEquals(expectedBytes.length, blobDescriptor.getSize());\n\n    DescriptorDigest expectedDigest =\n        Digests.computeDigest(new ByteArrayInputStream(expectedBytes)).getDigest();\n    Assert.assertEquals(expectedDigest, blobDescriptor.getDigest());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/ProgressEventDispatcherTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder;\n\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport java.util.List;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ProgressEventDispatcher}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ProgressEventDispatcherTest {\n\n  @Mock private EventHandlers mockEventHandlers;\n\n  @Test\n  public void testDispatch() {\n    try (ProgressEventDispatcher progressEventDispatcher =\n            ProgressEventDispatcher.newRoot(mockEventHandlers, \"ignored\", 10);\n        ProgressEventDispatcher ignored =\n            progressEventDispatcher.newChildProducer().create(\"ignored\", 20)) {\n      // empty\n    }\n\n    ArgumentCaptor<ProgressEvent> progressEventArgumentCaptor =\n        ArgumentCaptor.forClass(ProgressEvent.class);\n    Mockito.verify(mockEventHandlers, Mockito.times(4))\n        .dispatch(progressEventArgumentCaptor.capture());\n    List<ProgressEvent> progressEvents = progressEventArgumentCaptor.getAllValues();\n\n    Assert.assertSame(progressEvents.get(0).getAllocation(), progressEvents.get(3).getAllocation());\n    Assert.assertSame(progressEvents.get(1).getAllocation(), progressEvents.get(2).getAllocation());\n\n    Assert.assertEquals(0, progressEvents.get(0).getUnits());\n    Assert.assertEquals(0, progressEvents.get(1).getUnits());\n    Assert.assertEquals(20, progressEvents.get(2).getUnits());\n    Assert.assertEquals(9, progressEvents.get(3).getUnits());\n  }\n\n  @Test\n  public void testDispatch_safeWithtooMuchProgress() {\n    try (ProgressEventDispatcher progressEventDispatcher =\n        ProgressEventDispatcher.newRoot(mockEventHandlers, \"allocation description\", 10)) {\n      progressEventDispatcher.dispatchProgress(6);\n      progressEventDispatcher.dispatchProgress(8);\n      progressEventDispatcher.dispatchProgress(1);\n    }\n\n    ArgumentCaptor<ProgressEvent> eventsCaptor = ArgumentCaptor.forClass(ProgressEvent.class);\n    Mockito.verify(mockEventHandlers, Mockito.times(4)).dispatch(eventsCaptor.capture());\n    List<ProgressEvent> progressEvents = eventsCaptor.getAllValues();\n\n    Assert.assertSame(progressEvents.get(0).getAllocation(), progressEvents.get(1).getAllocation());\n    Assert.assertSame(progressEvents.get(1).getAllocation(), progressEvents.get(2).getAllocation());\n    Assert.assertSame(progressEvents.get(2).getAllocation(), progressEvents.get(3).getAllocation());\n\n    Assert.assertEquals(10, progressEvents.get(0).getAllocation().getAllocationUnits());\n\n    Assert.assertEquals(0, progressEvents.get(0).getUnits());\n    Assert.assertEquals(6, progressEvents.get(1).getUnits());\n    Assert.assertEquals(4, progressEvents.get(2).getUnits());\n    Assert.assertEquals(0, progressEvents.get(3).getUnits());\n  }\n\n  @Test\n  public void testDispatch_safeWithTooManyChildren() {\n    try (ProgressEventDispatcher progressEventDispatcher =\n            ProgressEventDispatcher.newRoot(mockEventHandlers, \"allocation description\", 1);\n        ProgressEventDispatcher ignored1 =\n            progressEventDispatcher.newChildProducer().create(\"ignored\", 5);\n        ProgressEventDispatcher ignored2 =\n            progressEventDispatcher.newChildProducer().create(\"ignored\", 4)) {\n      // empty\n    }\n\n    ArgumentCaptor<ProgressEvent> eventsCaptor = ArgumentCaptor.forClass(ProgressEvent.class);\n    Mockito.verify(mockEventHandlers, Mockito.times(5)).dispatch(eventsCaptor.capture());\n    List<ProgressEvent> progressEvents = eventsCaptor.getAllValues();\n\n    Assert.assertEquals(1, progressEvents.get(0).getAllocation().getAllocationUnits());\n    Assert.assertEquals(5, progressEvents.get(1).getAllocation().getAllocationUnits());\n    Assert.assertEquals(4, progressEvents.get(2).getAllocation().getAllocationUnits());\n\n    // child1 (of allocation 5) opening and closing\n    Assert.assertSame(progressEvents.get(1).getAllocation(), progressEvents.get(4).getAllocation());\n    // child1 (of allocation 4) opening and closing\n    Assert.assertSame(progressEvents.get(2).getAllocation(), progressEvents.get(3).getAllocation());\n\n    Assert.assertEquals(0, progressEvents.get(0).getUnits()); // 0-progress sent when root creation\n    Assert.assertEquals(0, progressEvents.get(1).getUnits()); // when child1 creation\n    Assert.assertEquals(0, progressEvents.get(2).getUnits()); // when child2 creation\n    Assert.assertEquals(4, progressEvents.get(3).getUnits()); // when child2 closes\n    Assert.assertEquals(5, progressEvents.get(4).getUnits()); // when child1 closes\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/TimerEventDispatcherTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder;\n\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport com.google.cloud.tools.jib.event.events.TimerEvent.State;\nimport java.time.Clock;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link TimerEventDispatcher}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class TimerEventDispatcherTest {\n\n  private final Deque<TimerEvent> timerEventQueue = new ArrayDeque<>();\n\n  @Mock private Clock mockClock;\n\n  @Test\n  public void testLogging() {\n    EventHandlers eventHandlers =\n        EventHandlers.builder().add(TimerEvent.class, timerEventQueue::add).build();\n\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH);\n    try (TimerEventDispatcher parentTimerEventDispatcher =\n        new TimerEventDispatcher(eventHandlers, \"description\", mockClock, null)) {\n      Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(1));\n      parentTimerEventDispatcher.lap();\n      Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(1).plusNanos(1));\n      try (TimerEventDispatcher ignored =\n          parentTimerEventDispatcher.subTimer(\"child description\")) {\n        Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(2));\n        // Laps on close.\n      }\n    }\n\n    TimerEvent timerEvent = getNextTimerEvent();\n    verifyNoParent(timerEvent);\n    verifyStartState(timerEvent);\n    verifyDescription(timerEvent, \"description\");\n\n    TimerEvent.Timer parentTimer = timerEvent.getTimer();\n\n    timerEvent = getNextTimerEvent();\n    verifyNoParent(timerEvent);\n    verifyStateFirstLap(timerEvent, State.LAP);\n    verifyDescription(timerEvent, \"description\");\n\n    timerEvent = getNextTimerEvent();\n    verifyParent(timerEvent, parentTimer);\n    verifyStartState(timerEvent);\n    verifyDescription(timerEvent, \"child description\");\n\n    timerEvent = getNextTimerEvent();\n    verifyParent(timerEvent, parentTimer);\n    verifyStateFirstLap(timerEvent, State.FINISHED);\n    verifyDescription(timerEvent, \"child description\");\n\n    timerEvent = getNextTimerEvent();\n    verifyNoParent(timerEvent);\n    verifyStateNotFirstLap(timerEvent, State.FINISHED);\n    verifyDescription(timerEvent, \"description\");\n\n    Assert.assertTrue(timerEventQueue.isEmpty());\n  }\n\n  /**\n   * Verifies that the {@code timerEvent}'s timer has no parent.\n   *\n   * @param timerEvent the {@link TimerEvent} to verify\n   */\n  private void verifyNoParent(TimerEvent timerEvent) {\n    Assert.assertFalse(timerEvent.getTimer().getParent().isPresent());\n  }\n\n  /**\n   * Verifies that the {@code timerEvent}'s timer has parent {@code expectedParentTimer}.\n   *\n   * @param timerEvent the {@link TimerEvent} to verify\n   * @param expectedParentTimer the expected parent timer\n   */\n  private void verifyParent(TimerEvent timerEvent, TimerEvent.Timer expectedParentTimer) {\n    Assert.assertTrue(timerEvent.getTimer().getParent().isPresent());\n    Assert.assertSame(expectedParentTimer, timerEvent.getTimer().getParent().get());\n  }\n\n  /**\n   * Verifies that the {@code timerEvent}'s state is {@link State#START}.\n   *\n   * @param timerEvent the {@link TimerEvent} to verify\n   */\n  private void verifyStartState(TimerEvent timerEvent) {\n    Assert.assertEquals(State.START, timerEvent.getState());\n    Assert.assertEquals(Duration.ZERO, timerEvent.getDuration());\n    Assert.assertEquals(Duration.ZERO, timerEvent.getElapsed());\n  }\n\n  /**\n   * Verifies that the {@code timerEvent}'s state is {@code expectedState} and that this is the\n   * first lap for the timer.\n   *\n   * @param timerEvent the {@link TimerEvent} to verify\n   * @param expectedState the expected {@link State}\n   */\n  private void verifyStateFirstLap(TimerEvent timerEvent, State expectedState) {\n    Assert.assertEquals(expectedState, timerEvent.getState());\n    Assert.assertTrue(timerEvent.getDuration().compareTo(Duration.ZERO) > 0);\n    Assert.assertEquals(0, timerEvent.getElapsed().compareTo(timerEvent.getDuration()));\n  }\n\n  /**\n   * Verifies that the {@code timerEvent}'s state is {@code expectedState} and that this is not the\n   * first lap for the timer.\n   *\n   * @param timerEvent the {@link TimerEvent} to verify\n   * @param expectedState the expected {@link State}\n   */\n  private void verifyStateNotFirstLap(TimerEvent timerEvent, State expectedState) {\n    Assert.assertEquals(expectedState, timerEvent.getState());\n    Assert.assertTrue(timerEvent.getDuration().compareTo(Duration.ZERO) > 0);\n    Assert.assertTrue(timerEvent.getElapsed().compareTo(timerEvent.getDuration()) > 0);\n  }\n\n  /**\n   * Verifies that the {@code timerEvent}'s description is {@code expectedDescription}.\n   *\n   * @param timerEvent the {@link TimerEvent} to verify\n   * @param expectedDescription the expected description\n   */\n  private void verifyDescription(TimerEvent timerEvent, String expectedDescription) {\n    Assert.assertEquals(expectedDescription, timerEvent.getDescription());\n  }\n\n  /**\n   * Gets the next {@link TimerEvent} on the {@link #timerEventQueue}.\n   *\n   * @return the next {@link TimerEvent}\n   */\n  private TimerEvent getNextTimerEvent() {\n    TimerEvent timerEvent = timerEventQueue.poll();\n    Assert.assertNotNull(timerEvent);\n    return timerEvent;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/TimerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder;\n\nimport java.time.Clock;\nimport java.time.Duration;\nimport java.time.Instant;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link Timer}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class TimerTest {\n\n  @Mock private Clock mockClock;\n\n  @Test\n  public void testLap() {\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH);\n    Timer parentTimer = new Timer(mockClock, null);\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(5));\n    Duration parentDuration1 = parentTimer.lap();\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(15));\n    Duration parentDuration2 = parentTimer.lap();\n\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(16));\n    Timer childTimer = new Timer(mockClock, parentTimer);\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(16).plusNanos(1));\n    Duration childDuration = childTimer.lap();\n\n    Mockito.when(mockClock.instant()).thenReturn(Instant.EPOCH.plusMillis(16).plusNanos(2));\n    Duration parentDuration3 = parentTimer.lap();\n\n    Assert.assertTrue(parentDuration2.compareTo(parentDuration1) > 0);\n    Assert.assertTrue(parentDuration1.compareTo(parentDuration3) > 0);\n    Assert.assertTrue(parentDuration3.compareTo(childDuration) > 0);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildAndCacheApplicationLayerStepTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.cache.CachedLayer;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link BuildAndCacheApplicationLayerStep}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class BuildAndCacheApplicationLayerStepTest {\n\n  // TODO: Consolidate with BuildStepsIntegrationTest.\n  private static final AbsoluteUnixPath EXTRACTION_PATH_ROOT =\n      AbsoluteUnixPath.get(\"/some/extraction/path/\");\n\n  private static final AbsoluteUnixPath EXTRA_FILES_LAYER_EXTRACTION_PATH =\n      AbsoluteUnixPath.get(\"/extra\");\n\n  /**\n   * Lists the files in the {@code resourcePath} resources directory and creates a {@link\n   * FileEntriesLayer} with entries from those files.\n   */\n  private static FileEntriesLayer makeLayerConfiguration(\n      String resourcePath, AbsoluteUnixPath extractionPath) throws URISyntaxException, IOException {\n    try (Stream<Path> fileStream =\n        Files.list(Paths.get(Resources.getResource(resourcePath).toURI()))) {\n      FileEntriesLayer.Builder layerConfigurationBuilder = FileEntriesLayer.builder();\n      fileStream.forEach(\n          sourceFile ->\n              layerConfigurationBuilder.addEntry(\n                  sourceFile, extractionPath.resolve(sourceFile.getFileName())));\n      return layerConfigurationBuilder.build();\n    }\n  }\n\n  private static void assertBlobsEqual(Blob expectedBlob, Blob blob) throws IOException {\n    Assert.assertArrayEquals(Blobs.writeToByteArray(expectedBlob), Blobs.writeToByteArray(blob));\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock private BuildContext mockBuildContext;\n\n  private Cache cache;\n\n  private FileEntriesLayer fakeDependenciesLayerConfiguration;\n  private FileEntriesLayer fakeSnapshotDependenciesLayerConfiguration;\n  private FileEntriesLayer fakeResourcesLayerConfiguration;\n  private FileEntriesLayer fakeClassesLayerConfiguration;\n  private FileEntriesLayer fakeExtraFilesLayerConfiguration;\n  private FileEntriesLayer emptyLayerConfiguration;\n\n  @Before\n  public void setUp() throws IOException, URISyntaxException, CacheDirectoryCreationException {\n    fakeDependenciesLayerConfiguration =\n        makeLayerConfiguration(\n            \"core/application/dependencies\", EXTRACTION_PATH_ROOT.resolve(\"libs\"));\n    fakeSnapshotDependenciesLayerConfiguration =\n        makeLayerConfiguration(\n            \"core/application/snapshot-dependencies\", EXTRACTION_PATH_ROOT.resolve(\"libs\"));\n    fakeResourcesLayerConfiguration =\n        makeLayerConfiguration(\n            \"core/application/resources\", EXTRACTION_PATH_ROOT.resolve(\"resources\"));\n    fakeClassesLayerConfiguration =\n        makeLayerConfiguration(\"core/application/classes\", EXTRACTION_PATH_ROOT.resolve(\"classes\"));\n    fakeExtraFilesLayerConfiguration =\n        FileEntriesLayer.builder()\n            .addEntry(\n                Paths.get(Resources.getResource(\"core/fileA\").toURI()),\n                EXTRA_FILES_LAYER_EXTRACTION_PATH.resolve(\"fileA\"))\n            .addEntry(\n                Paths.get(Resources.getResource(\"core/fileB\").toURI()),\n                EXTRA_FILES_LAYER_EXTRACTION_PATH.resolve(\"fileB\"))\n            .build();\n    emptyLayerConfiguration = FileEntriesLayer.builder().build();\n\n    cache = Cache.withDirectory(temporaryFolder.newFolder().toPath());\n\n    Mockito.when(mockBuildContext.getEventHandlers()).thenReturn(EventHandlers.NONE);\n    Mockito.when(mockBuildContext.getApplicationLayersCache()).thenReturn(cache);\n  }\n\n  private List<Layer> buildFakeLayersToCache()\n      throws LayerPropertyNotFoundException, IOException, CacheCorruptedException {\n    List<Layer> applicationLayers = new ArrayList<>();\n\n    ImmutableList<BuildAndCacheApplicationLayerStep> buildAndCacheApplicationLayerSteps =\n        BuildAndCacheApplicationLayerStep.makeList(\n            mockBuildContext,\n            ProgressEventDispatcher.newRoot(EventHandlers.NONE, \"ignored\", 1).newChildProducer());\n\n    for (BuildAndCacheApplicationLayerStep buildAndCacheApplicationLayerStep :\n        buildAndCacheApplicationLayerSteps) {\n      applicationLayers.add(buildAndCacheApplicationLayerStep.call());\n    }\n\n    return applicationLayers;\n  }\n\n  @Test\n  public void testRun()\n      throws LayerPropertyNotFoundException, IOException, CacheCorruptedException {\n    ImmutableList<FileEntriesLayer> fakeLayerConfigurations =\n        ImmutableList.of(\n            fakeDependenciesLayerConfiguration,\n            fakeSnapshotDependenciesLayerConfiguration,\n            fakeResourcesLayerConfiguration,\n            fakeClassesLayerConfiguration,\n            fakeExtraFilesLayerConfiguration);\n    Mockito.when(mockBuildContext.getLayerConfigurations()).thenReturn(fakeLayerConfigurations);\n\n    // Populates the cache.\n    List<Layer> applicationLayers = buildFakeLayersToCache();\n    Assert.assertEquals(5, applicationLayers.size());\n\n    ImmutableList<FileEntry> dependenciesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(0).getEntries());\n    ImmutableList<FileEntry> snapshotDependenciesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(1).getEntries());\n    ImmutableList<FileEntry> resourcesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(2).getEntries());\n    ImmutableList<FileEntry> classesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(3).getEntries());\n    ImmutableList<FileEntry> extraFilesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(4).getEntries());\n\n    CachedLayer dependenciesCachedLayer =\n        cache.retrieve(dependenciesLayerEntries).orElseThrow(AssertionError::new);\n    CachedLayer snapshotDependenciesCachedLayer =\n        cache.retrieve(snapshotDependenciesLayerEntries).orElseThrow(AssertionError::new);\n    CachedLayer resourcesCachedLayer =\n        cache.retrieve(resourcesLayerEntries).orElseThrow(AssertionError::new);\n    CachedLayer classesCachedLayer =\n        cache.retrieve(classesLayerEntries).orElseThrow(AssertionError::new);\n    CachedLayer extraFilesCachedLayer =\n        cache.retrieve(extraFilesLayerEntries).orElseThrow(AssertionError::new);\n\n    // Verifies that the cached layers are up-to-date.\n    Assert.assertEquals(\n        applicationLayers.get(0).getBlobDescriptor().getDigest(),\n        dependenciesCachedLayer.getDigest());\n    Assert.assertEquals(\n        applicationLayers.get(1).getBlobDescriptor().getDigest(),\n        snapshotDependenciesCachedLayer.getDigest());\n    Assert.assertEquals(\n        applicationLayers.get(2).getBlobDescriptor().getDigest(), resourcesCachedLayer.getDigest());\n    Assert.assertEquals(\n        applicationLayers.get(3).getBlobDescriptor().getDigest(), classesCachedLayer.getDigest());\n    Assert.assertEquals(\n        applicationLayers.get(4).getBlobDescriptor().getDigest(),\n        extraFilesCachedLayer.getDigest());\n\n    // Verifies that the cache reader gets the same layers as the newest application layers.\n    assertBlobsEqual(applicationLayers.get(0).getBlob(), dependenciesCachedLayer.getBlob());\n    assertBlobsEqual(applicationLayers.get(1).getBlob(), snapshotDependenciesCachedLayer.getBlob());\n    assertBlobsEqual(applicationLayers.get(2).getBlob(), resourcesCachedLayer.getBlob());\n    assertBlobsEqual(applicationLayers.get(3).getBlob(), classesCachedLayer.getBlob());\n    assertBlobsEqual(applicationLayers.get(4).getBlob(), extraFilesCachedLayer.getBlob());\n  }\n\n  @Test\n  public void testRun_emptyLayersIgnored() throws IOException, CacheCorruptedException {\n    ImmutableList<FileEntriesLayer> fakeLayerConfigurations =\n        ImmutableList.of(\n            fakeDependenciesLayerConfiguration,\n            emptyLayerConfiguration,\n            fakeResourcesLayerConfiguration,\n            fakeClassesLayerConfiguration,\n            emptyLayerConfiguration);\n    Mockito.when(mockBuildContext.getLayerConfigurations()).thenReturn(fakeLayerConfigurations);\n\n    // Populates the cache.\n    List<Layer> applicationLayers = buildFakeLayersToCache();\n    Assert.assertEquals(3, applicationLayers.size());\n\n    ImmutableList<FileEntry> dependenciesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(0).getEntries());\n    ImmutableList<FileEntry> resourcesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(2).getEntries());\n    ImmutableList<FileEntry> classesLayerEntries =\n        ImmutableList.copyOf(fakeLayerConfigurations.get(3).getEntries());\n\n    CachedLayer dependenciesCachedLayer =\n        cache.retrieve(dependenciesLayerEntries).orElseThrow(AssertionError::new);\n    CachedLayer resourcesCachedLayer =\n        cache.retrieve(resourcesLayerEntries).orElseThrow(AssertionError::new);\n    CachedLayer classesCachedLayer =\n        cache.retrieve(classesLayerEntries).orElseThrow(AssertionError::new);\n\n    // Verifies that the cached layers are up-to-date.\n    Assert.assertEquals(\n        applicationLayers.get(0).getBlobDescriptor().getDigest(),\n        dependenciesCachedLayer.getDigest());\n    Assert.assertEquals(\n        applicationLayers.get(1).getBlobDescriptor().getDigest(), resourcesCachedLayer.getDigest());\n    Assert.assertEquals(\n        applicationLayers.get(2).getBlobDescriptor().getDigest(), classesCachedLayer.getDigest());\n\n    // Verifies that the cache reader gets the same layers as the newest application layers.\n    assertBlobsEqual(applicationLayers.get(0).getBlob(), dependenciesCachedLayer.getBlob());\n    assertBlobsEqual(applicationLayers.get(1).getBlob(), resourcesCachedLayer.getBlob());\n    assertBlobsEqual(applicationLayers.get(2).getBlob(), classesCachedLayer.getBlob());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildImageStepTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.cache.CachedLayer;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.configuration.DockerHealthCheck;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.HistoryEntry;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.security.DigestException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link BuildImageStep}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class BuildImageStepTest {\n\n  @Mock private ProgressEventDispatcher.Factory mockProgressEventDispatcherFactory;\n  @Mock private BuildContext mockBuildContext;\n  @Mock private ContainerConfiguration mockContainerConfiguration;\n  @Mock private CachedLayer mockCachedLayer;\n\n  private Image baseImage;\n  private List<PreparedLayer> baseImageLayers;\n  private List<PreparedLayer> applicationLayers;\n\n  private DescriptorDigest testDescriptorDigest;\n  private HistoryEntry nonEmptyLayerHistory;\n  private HistoryEntry emptyLayerHistory;\n\n  @Before\n  public void setUp() throws DigestException {\n    testDescriptorDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n\n    Mockito.when(mockBuildContext.getEventHandlers()).thenReturn(EventHandlers.NONE);\n    Mockito.when(mockBuildContext.getContainerConfiguration())\n        .thenReturn(mockContainerConfiguration);\n    Mockito.when(mockBuildContext.getToolName()).thenReturn(\"jib\");\n    Mockito.when(mockContainerConfiguration.getCreationTime()).thenReturn(Instant.EPOCH);\n    Mockito.when(mockContainerConfiguration.getEnvironmentMap()).thenReturn(ImmutableMap.of());\n    Mockito.when(mockContainerConfiguration.getProgramArguments()).thenReturn(ImmutableList.of());\n    Mockito.when(mockContainerConfiguration.getExposedPorts()).thenReturn(ImmutableSet.of());\n    Mockito.when(mockContainerConfiguration.getEntrypoint()).thenReturn(ImmutableList.of());\n    Mockito.when(mockContainerConfiguration.getUser()).thenReturn(\"root\");\n    Mockito.when(mockCachedLayer.getBlobDescriptor())\n        .thenReturn(new BlobDescriptor(0, testDescriptorDigest));\n\n    nonEmptyLayerHistory =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"JibBase\")\n            .setCreatedBy(\"jib-test\")\n            .build();\n    emptyLayerHistory =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"JibBase\")\n            .setCreatedBy(\"jib-test\")\n            .setEmptyLayer(true)\n            .build();\n\n    baseImage =\n        Image.builder(V22ManifestTemplate.class)\n            .setArchitecture(\"wasm\")\n            .setOs(\"js\")\n            .addEnvironment(ImmutableMap.of(\"BASE_ENV\", \"BASE_ENV_VALUE\", \"BASE_ENV_2\", \"DEFAULT\"))\n            .addLabel(\"base.label\", \"base.label.value\")\n            .addLabel(\"base.label.2\", \"default\")\n            .setUser(\"base:user\")\n            .setWorkingDirectory(\"/base/working/directory\")\n            .setEntrypoint(ImmutableList.of(\"baseImageEntrypoint\"))\n            .setProgramArguments(ImmutableList.of(\"catalina.sh\", \"run\"))\n            .setHealthCheck(\n                DockerHealthCheck.fromCommand(ImmutableList.of(\"CMD-SHELL\", \"echo hi\"))\n                    .setInterval(Duration.ofSeconds(3))\n                    .setTimeout(Duration.ofSeconds(2))\n                    .setStartPeriod(Duration.ofSeconds(1))\n                    .setRetries(20)\n                    .build())\n            .addExposedPorts(ImmutableSet.of(Port.tcp(1000), Port.udp(2000)))\n            .addVolumes(\n                ImmutableSet.of(\n                    AbsoluteUnixPath.get(\"/base/path1\"), AbsoluteUnixPath.get(\"/base/path2\")))\n            .addHistory(nonEmptyLayerHistory)\n            .addHistory(emptyLayerHistory)\n            .addHistory(emptyLayerHistory)\n            .build();\n    baseImageLayers =\n        Arrays.asList(\n            new PreparedLayer.Builder(mockCachedLayer).build(),\n            new PreparedLayer.Builder(mockCachedLayer).build(),\n            new PreparedLayer.Builder(mockCachedLayer).build());\n    applicationLayers =\n        Arrays.asList(\n            new PreparedLayer.Builder(mockCachedLayer).setName(\"dependencies\").build(),\n            new PreparedLayer.Builder(mockCachedLayer).setName(\"resources\").build(),\n            new PreparedLayer.Builder(mockCachedLayer).setName(\"classes\").build(),\n            new PreparedLayer.Builder(mockCachedLayer).setName(\"extra files\").build());\n  }\n\n  @Test\n  public void test_basicCase() {\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n    Assert.assertEquals(\"root\", image.getUser());\n    Assert.assertEquals(\n        testDescriptorDigest, image.getLayers().get(0).getBlobDescriptor().getDigest());\n  }\n\n  @Test\n  public void test_propagateBaseImageConfiguration() {\n    Mockito.when(mockContainerConfiguration.getEnvironmentMap())\n        .thenReturn(ImmutableMap.of(\"MY_ENV\", \"MY_ENV_VALUE\", \"BASE_ENV_2\", \"NEW_VALUE\"));\n    Mockito.when(mockContainerConfiguration.getLabels())\n        .thenReturn(ImmutableMap.of(\"my.label\", \"my.label.value\", \"base.label.2\", \"new.value\"));\n    Mockito.when(mockContainerConfiguration.getExposedPorts())\n        .thenReturn(ImmutableSet.of(Port.tcp(3000), Port.udp(4000)));\n    Mockito.when(mockContainerConfiguration.getVolumes())\n        .thenReturn(\n            ImmutableSet.of(\n                AbsoluteUnixPath.get(\"/new/path1\"), AbsoluteUnixPath.get(\"/new/path2\")));\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n    Assert.assertEquals(\"wasm\", image.getArchitecture());\n    Assert.assertEquals(\"js\", image.getOs());\n    Assert.assertEquals(\n        ImmutableMap.of(\n            \"BASE_ENV\", \"BASE_ENV_VALUE\", \"MY_ENV\", \"MY_ENV_VALUE\", \"BASE_ENV_2\", \"NEW_VALUE\"),\n        image.getEnvironment());\n    Assert.assertEquals(\n        ImmutableMap.of(\n            \"base.label\",\n            \"base.label.value\",\n            \"my.label\",\n            \"my.label.value\",\n            \"base.label.2\",\n            \"new.value\"),\n        image.getLabels());\n    Assert.assertNotNull(image.getHealthCheck());\n    Assert.assertEquals(\n        ImmutableList.of(\"CMD-SHELL\", \"echo hi\"), image.getHealthCheck().getCommand());\n    Assert.assertTrue(image.getHealthCheck().getInterval().isPresent());\n    Assert.assertEquals(Duration.ofSeconds(3), image.getHealthCheck().getInterval().get());\n    Assert.assertTrue(image.getHealthCheck().getTimeout().isPresent());\n    Assert.assertEquals(Duration.ofSeconds(2), image.getHealthCheck().getTimeout().get());\n    Assert.assertTrue(image.getHealthCheck().getStartPeriod().isPresent());\n    Assert.assertEquals(Duration.ofSeconds(1), image.getHealthCheck().getStartPeriod().get());\n    Assert.assertTrue(image.getHealthCheck().getRetries().isPresent());\n    Assert.assertEquals(20, (int) image.getHealthCheck().getRetries().get());\n    Assert.assertEquals(\n        ImmutableSet.of(Port.tcp(1000), Port.udp(2000), Port.tcp(3000), Port.udp(4000)),\n        image.getExposedPorts());\n    Assert.assertEquals(\n        ImmutableSet.of(\n            AbsoluteUnixPath.get(\"/base/path1\"),\n            AbsoluteUnixPath.get(\"/base/path2\"),\n            AbsoluteUnixPath.get(\"/new/path1\"),\n            AbsoluteUnixPath.get(\"/new/path2\")),\n        image.getVolumes());\n    Assert.assertEquals(\"/base/working/directory\", image.getWorkingDirectory());\n    Assert.assertEquals(\"root\", image.getUser());\n\n    Assert.assertEquals(image.getHistory().get(0), nonEmptyLayerHistory);\n    Assert.assertEquals(image.getHistory().get(1), emptyLayerHistory);\n    Assert.assertEquals(image.getHistory().get(2), emptyLayerHistory);\n    Assert.assertEquals(ImmutableList.of(), image.getEntrypoint());\n    Assert.assertEquals(ImmutableList.of(), image.getProgramArguments());\n  }\n\n  @Test\n  public void testOverrideWorkingDirectory() {\n    Mockito.when(mockContainerConfiguration.getWorkingDirectory())\n        .thenReturn(AbsoluteUnixPath.get(\"/my/directory\"));\n\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n\n    Assert.assertEquals(\"/my/directory\", image.getWorkingDirectory());\n  }\n\n  @Test\n  public void test_inheritedUser() {\n    Mockito.when(mockContainerConfiguration.getUser()).thenReturn(null);\n\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n\n    Assert.assertEquals(\"base:user\", image.getUser());\n  }\n\n  @Test\n  public void test_inheritedEntrypoint() {\n    Mockito.when(mockContainerConfiguration.getEntrypoint()).thenReturn(null);\n    Mockito.when(mockContainerConfiguration.getProgramArguments())\n        .thenReturn(ImmutableList.of(\"test\"));\n\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n\n    Assert.assertEquals(ImmutableList.of(\"baseImageEntrypoint\"), image.getEntrypoint());\n    Assert.assertEquals(ImmutableList.of(\"test\"), image.getProgramArguments());\n  }\n\n  @Test\n  public void test_inheritedEntrypointAndProgramArguments() {\n    Mockito.when(mockContainerConfiguration.getEntrypoint()).thenReturn(null);\n    Mockito.when(mockContainerConfiguration.getProgramArguments()).thenReturn(null);\n\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n\n    Assert.assertEquals(ImmutableList.of(\"baseImageEntrypoint\"), image.getEntrypoint());\n    Assert.assertEquals(ImmutableList.of(\"catalina.sh\", \"run\"), image.getProgramArguments());\n  }\n\n  @Test\n  public void test_notInheritedProgramArguments() {\n    Mockito.when(mockContainerConfiguration.getEntrypoint())\n        .thenReturn(ImmutableList.of(\"myEntrypoint\"));\n    Mockito.when(mockContainerConfiguration.getProgramArguments()).thenReturn(null);\n\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n\n    Assert.assertEquals(ImmutableList.of(\"myEntrypoint\"), image.getEntrypoint());\n    Assert.assertNull(image.getProgramArguments());\n  }\n\n  @Test\n  public void test_generateHistoryObjects() {\n    Image image =\n        new BuildImageStep(\n                mockBuildContext,\n                mockProgressEventDispatcherFactory,\n                baseImage,\n                baseImageLayers,\n                applicationLayers)\n            .call();\n\n    // Make sure history is as expected\n    HistoryEntry expectedAddedBaseLayerHistory =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setComment(\"auto-generated by Jib\")\n            .build();\n\n    HistoryEntry expectedApplicationLayerHistoryDependencies =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"Jib\")\n            .setCreatedBy(\"jib:null\")\n            .setComment(\"dependencies\")\n            .build();\n\n    HistoryEntry expectedApplicationLayerHistoryResources =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"Jib\")\n            .setCreatedBy(\"jib:null\")\n            .setComment(\"resources\")\n            .build();\n\n    HistoryEntry expectedApplicationLayerHistoryClasses =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"Jib\")\n            .setCreatedBy(\"jib:null\")\n            .setComment(\"classes\")\n            .build();\n\n    HistoryEntry expectedApplicationLayerHistoryExtrafiles =\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"Jib\")\n            .setCreatedBy(\"jib:null\")\n            .setComment(\"extra files\")\n            .build();\n\n    // Base layers (1 non-empty propagated, 2 empty propagated, 2 non-empty generated)\n    Assert.assertEquals(nonEmptyLayerHistory, image.getHistory().get(0));\n    Assert.assertEquals(emptyLayerHistory, image.getHistory().get(1));\n    Assert.assertEquals(emptyLayerHistory, image.getHistory().get(2));\n    Assert.assertEquals(expectedAddedBaseLayerHistory, image.getHistory().get(3));\n    Assert.assertEquals(expectedAddedBaseLayerHistory, image.getHistory().get(4));\n\n    // Application layers (4 generated)\n    Assert.assertEquals(expectedApplicationLayerHistoryDependencies, image.getHistory().get(5));\n    Assert.assertEquals(expectedApplicationLayerHistoryResources, image.getHistory().get(6));\n    Assert.assertEquals(expectedApplicationLayerHistoryClasses, image.getHistory().get(7));\n    Assert.assertEquals(expectedApplicationLayerHistoryExtrafiles, image.getHistory().get(8));\n\n    // Should be exactly 9 total\n    Assert.assertEquals(9, image.getHistory().size());\n  }\n\n  @Test\n  public void testTruncateLongClasspath_shortClasspath() {\n    ImmutableList<String> entrypoint =\n        ImmutableList.of(\n            \"java\", \"-Dmy-property=value\", \"-cp\", \"/app/classes:/app/libs/*\", \"com.example.Main\");\n\n    Assert.assertEquals(\n        \"[java, -Dmy-property=value, -cp, /app/classes:/app/libs/*, com.example.Main]\",\n        BuildImageStep.truncateLongClasspath(entrypoint));\n  }\n\n  @Test\n  public void testTruncateLongClasspath_longClasspath() {\n    String classpath =\n        \"/app/resources:/app/classes:/app/libs/spring-boot-starter-web-2.0.3.RELEASE.jar:/app/libs/\"\n            + \"shared-library-0.1.0.jar:/app/libs/spring-boot-starter-json-2.0.3.RELEASE.jar:/app/\"\n            + \"libs/spring-boot-starter-2.0.3.RELEASE.jar:/app/libs/spring-boot-starter-tomcat-2.0.\"\n            + \"3.RELEASE.jar\";\n    ImmutableList<String> entrypoint =\n        ImmutableList.of(\"java\", \"-Dmy-property=value\", \"-cp\", classpath, \"com.example.Main\");\n\n    Assert.assertEquals(\n        \"[java, -Dmy-property=value, -cp, /app/resources:/app/classes:/app/libs/spring-boot-starter\"\n            + \"-web-2.0.3.RELEASE.jar:/app/libs/shared-library-0.1.0.jar:/app/libs/spring-boot-\"\n            + \"starter-json-2.0.3.RELEASE.jar:/app/libs/spring-boot-starter-2.<... classpath \"\n            + \"truncated ...>, com.example.Main]\",\n        BuildImageStep.truncateLongClasspath(entrypoint));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStepTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link BuildManifestListOrSingleManifest}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class BuildManifestListOrSingleManifestStepTest {\n\n  @Mock private ProgressEventDispatcher.Factory progressDispatcherFactory;\n  @Mock private BuildContext buildContext;\n  @Mock private Layer layer;\n\n  private Image image1;\n  private Image image2;\n\n  @Before\n  public void setUp() {\n    Mockito.when(buildContext.getEventHandlers()).thenReturn(EventHandlers.NONE);\n    Mockito.doReturn(V22ManifestTemplate.class).when(buildContext).getTargetFormat();\n    Mockito.when(layer.getBlobDescriptor()).thenReturn(new BlobDescriptor(0, null));\n\n    image1 =\n        Image.builder(V22ManifestTemplate.class)\n            .setArchitecture(\"amd64\")\n            .setOs(\"linux\")\n            .addLayer(layer)\n            .build();\n    image2 =\n        Image.builder(V22ManifestTemplate.class)\n            .setArchitecture(\"arm64\")\n            .setOs(\"windows\")\n            .addLayer(layer)\n            .build();\n  }\n\n  @Test\n  public void testCall_singleManifest() throws IOException {\n\n    // Expected manifest JSON\n    //  {\n    //  \"schemaVersion\":2,\n    //  \"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n    //  \"config\":{\n    //    \"mediaType\":\"application/vnd.docker.container.image.v1+json\",\n    //    \"digest\":\"sha256:1b2ff280940537177565443144a81319ad48528fd35d1cdc38cbde07f24f6912\",\n    //    \"size\":158\n    //  },\n    //  \"layers\":[\n    //    {\n    //      \"mediaType\":\"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n    //      \"size\":0\n    //    }\n    //  ]\n    // }\n\n    ManifestTemplate manifestTemplate =\n        new BuildManifestListOrSingleManifestStep(\n                buildContext, progressDispatcherFactory, Arrays.asList(image1))\n            .call();\n\n    Assert.assertTrue(manifestTemplate instanceof V22ManifestTemplate);\n    V22ManifestTemplate manifest = (V22ManifestTemplate) manifestTemplate;\n    Assert.assertEquals(2, manifest.getSchemaVersion());\n    Assert.assertEquals(\n        \"application/vnd.docker.distribution.manifest.v2+json\", manifest.getManifestMediaType());\n    Assert.assertEquals(\n        \"sha256:1b2ff280940537177565443144a81319ad48528fd35d1cdc38cbde07f24f6912\",\n        manifest.getContainerConfiguration().getDigest().toString());\n    Assert.assertEquals(0, manifest.getLayers().get(0).getSize());\n    Assert.assertEquals(158, manifest.getContainerConfiguration().getSize());\n  }\n\n  @Test\n  public void testCall_manifestList() throws IOException {\n\n    // Expected Manifest List JSON\n    //  {\n    //  \"schemaVersion\":2,\n    //  \"mediaType\":\"application/vnd.docker.distribution.manifest.list.v2+json\",\n    //  \"manifests\":[\n    //    {\n    //      \"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n    //      \"digest\":\"sha256:9467fc431ac5dd84dafdc13f75111fc467cd57aff4732edda8c9e0bbcabe0183\",\n    //      \"size\":338,\n    //      \"platform\":{\n    //        \"architecture\":\"amd64\",\n    //        \"os\":\"linux\"\n    //      }\n    //    },\n    //    {\n    //      \"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n    //      \"digest\":\"sha256:439351c848845c46a3952f28416992b66003361d00943b6cdb04b6d5533f02bf\",\n    //      \"size\":338,\n    //      \"platform\":{\n    //        \"architecture\":\"arm64\",\n    //        \"os\":\"windows\"\n    //      }\n    //    }\n    //  ]\n    // }\n\n    ManifestTemplate manifestTemplate =\n        new BuildManifestListOrSingleManifestStep(\n                buildContext, progressDispatcherFactory, Arrays.asList(image1, image2))\n            .call();\n\n    Assert.assertTrue(manifestTemplate instanceof V22ManifestListTemplate);\n    V22ManifestListTemplate manifestList = (V22ManifestListTemplate) manifestTemplate;\n    Assert.assertEquals(2, manifestList.getSchemaVersion());\n    Assert.assertEquals(\n        Arrays.asList(\"sha256:9467fc431ac5dd84dafdc13f75111fc467cd57aff4732edda8c9e0bbcabe0183\"),\n        manifestList.getDigestsForPlatform(\"amd64\", \"linux\"));\n    Assert.assertEquals(\n        Arrays.asList(\"sha256:439351c848845c46a3952f28416992b66003361d00943b6cdb04b6d5533f02bf\"),\n        manifestList.getDigestsForPlatform(\"arm64\", \"windows\"));\n  }\n\n  @Test\n  public void testCall_emptyImagesList() throws IOException {\n    try {\n      new BuildManifestListOrSingleManifestStep(\n              buildContext, progressDispatcherFactory, Collections.emptyList())\n          .call();\n      Assert.fail();\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\"no images given\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildResultTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/** Tests for {@link BuildResult}. */\npublic class BuildResultTest {\n\n  private DescriptorDigest digest1;\n  private DescriptorDigest digest2;\n  private DescriptorDigest id;\n  private DescriptorDigest id2;\n\n  @Before\n  public void setUp() throws DigestException {\n    digest1 =\n        DescriptorDigest.fromDigest(\n            \"sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789\");\n    digest2 =\n        DescriptorDigest.fromDigest(\n            \"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\");\n    id =\n        DescriptorDigest.fromDigest(\n            \"sha256:9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba\");\n\n    id2 =\n        DescriptorDigest.fromDigest(\n            \"sha256:1234543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba\");\n  }\n\n  @Test\n  public void testCreated() {\n    BuildResult container = new BuildResult(digest1, id, true);\n    Assert.assertEquals(digest1, container.getImageDigest());\n    Assert.assertEquals(id, container.getImageId());\n    Assert.assertTrue(container.isImagePushed());\n  }\n\n  @Test\n  public void testEquality() {\n    BuildResult container1 = new BuildResult(digest1, id, true);\n    BuildResult container2 = new BuildResult(digest1, id, true);\n    BuildResult container3 = new BuildResult(digest2, id, true);\n    BuildResult container4 = new BuildResult(digest1, id, false);\n    BuildResult container5 = new BuildResult(digest1, id2, false);\n\n    Assert.assertEquals(container1, container2);\n    Assert.assertEquals(container1, container1);\n    Assert.assertNotEquals(container1, container5);\n    Assert.assertNotEquals(container1, new Object());\n\n    Assert.assertEquals(container1.hashCode(), container2.hashCode());\n    Assert.assertEquals(container1.hashCode(), container4.hashCode());\n    Assert.assertNotEquals(container1, container3);\n    Assert.assertNotEquals(container1, container4);\n  }\n\n  @Test\n  public void testFromImage() throws IOException {\n    Image image1 = Image.builder(V22ManifestTemplate.class).setUser(\"user\").build();\n    Image image2 = Image.builder(V22ManifestTemplate.class).setUser(\"user\").build();\n    Image image3 = Image.builder(V22ManifestTemplate.class).setUser(\"anotherUser\").build();\n    Assert.assertEquals(\n        BuildResult.fromImage(image1, V22ManifestTemplate.class),\n        BuildResult.fromImage(image2, V22ManifestTemplate.class));\n    Assert.assertNotEquals(\n        BuildResult.fromImage(image1, V22ManifestTemplate.class),\n        BuildResult.fromImage(image3, V22ManifestTemplate.class));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageStepsTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.ImageDetails;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Optional;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n@RunWith(MockitoJUnitRunner.class)\npublic class LocalBaseImageStepsTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private final TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n\n  @Mock private BuildContext buildContext;\n  @Mock private EventHandlers eventHandlers;\n  @Mock private ProgressEventDispatcher.Factory progressEventDispatcherFactory;\n  @Mock private ProgressEventDispatcher progressEventDispatcher;\n  @Mock private ProgressEventDispatcher.Factory childFactory;\n  @Mock private ProgressEventDispatcher childDispatcher;\n\n  private static Path getResource(String resource) throws URISyntaxException {\n    return Paths.get(Resources.getResource(resource).toURI());\n  }\n\n  @Before\n  public void setup() throws IOException, CacheDirectoryCreationException {\n    Mockito.when(buildContext.getExecutorService())\n        .thenReturn(MoreExecutors.newDirectExecutorService());\n    Mockito.when(buildContext.getBaseImageLayersCache())\n        .thenReturn(Cache.withDirectory(temporaryFolder.newFolder().toPath()));\n    Mockito.when(buildContext.getEventHandlers()).thenReturn(eventHandlers);\n    Mockito.when(progressEventDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong()))\n        .thenReturn(progressEventDispatcher);\n    Mockito.when(progressEventDispatcher.newChildProducer()).thenReturn(childFactory);\n    Mockito.when(childFactory.create(Mockito.anyString(), Mockito.anyLong()))\n        .thenReturn(childDispatcher);\n  }\n\n  @After\n  public void tearDown() {\n    tempDirectoryProvider.close();\n  }\n\n  @Test\n  public void testCacheDockerImageTar_validDocker() throws Exception {\n    Path dockerBuild = getResource(\"core/extraction/docker-save.tar\");\n    LocalImage result =\n        LocalBaseImageSteps.cacheDockerImageTar(\n            buildContext, dockerBuild, progressEventDispatcherFactory, tempDirectoryProvider);\n\n    Mockito.verify(progressEventDispatcher, Mockito.times(2)).newChildProducer();\n    Assert.assertEquals(2, result.layers.size());\n    Assert.assertEquals(\n        \"5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd\",\n        result.layers.get(0).get().getDiffId().getHash());\n    Assert.assertEquals(\n        \"0011328ac5dfe3dde40c7c5e0e00c98d1833a3aeae2bfb668cf9eb965c229c7f\",\n        result.layers.get(0).get().getBlobDescriptor().getDigest().getHash());\n    Assert.assertEquals(\n        \"f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e\",\n        result.layers.get(1).get().getDiffId().getHash());\n    Assert.assertEquals(\n        \"c10ef24a5cef5092bbcb5a5666721cff7b86ce978c203a958d1fc86ee6c19f94\",\n        result.layers.get(1).get().getBlobDescriptor().getDigest().getHash());\n    Assert.assertEquals(2, result.configurationTemplate.getLayerCount());\n  }\n\n  @Test\n  public void testCacheDockerImageTar_validTar() throws Exception {\n    Path tarBuild = getResource(\"core/extraction/jib-image.tar\");\n    LocalImage result =\n        LocalBaseImageSteps.cacheDockerImageTar(\n            buildContext, tarBuild, progressEventDispatcherFactory, tempDirectoryProvider);\n\n    Mockito.verify(progressEventDispatcher, Mockito.times(2)).newChildProducer();\n    Assert.assertEquals(2, result.layers.size());\n    Assert.assertEquals(\n        \"5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd\",\n        result.layers.get(0).get().getDiffId().getHash());\n    Assert.assertEquals(\n        \"0011328ac5dfe3dde40c7c5e0e00c98d1833a3aeae2bfb668cf9eb965c229c7f\",\n        result.layers.get(0).get().getBlobDescriptor().getDigest().getHash());\n    Assert.assertEquals(\n        \"f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e\",\n        result.layers.get(1).get().getDiffId().getHash());\n    Assert.assertEquals(\n        \"c10ef24a5cef5092bbcb5a5666721cff7b86ce978c203a958d1fc86ee6c19f94\",\n        result.layers.get(1).get().getBlobDescriptor().getDigest().getHash());\n    Assert.assertEquals(2, result.configurationTemplate.getLayerCount());\n  }\n\n  @Test\n  public void testGetCachedDockerImage()\n      throws IOException, DigestException, CacheDirectoryCreationException, CacheCorruptedException,\n          URISyntaxException {\n    String dockerInspectJson =\n        \"{\\\"Size\\\": 0,\"\n            + \"\\\"Id\\\": \\\"sha256:066872f17ae819f846a6d5abcfc3165abe13fb0a157640fa8cb7af81077670c0\\\",\"\n            + \"\\\"RootFS\\\": { \\\"Layers\\\": [\"\n            + \"  \\\"sha256:5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd\\\",\"\n            + \"  \\\"sha256:f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e\\\" ] } }\";\n    ImageDetails dockerImageDetails =\n        JsonTemplateMapper.readJson(dockerInspectJson, CliDockerClient.DockerImageDetails.class);\n    Path cachePath = temporaryFolder.newFolder(\"cache\").toPath();\n    Files.createDirectories(cachePath.resolve(\"local/config\"));\n    Cache cache = Cache.withDirectory(cachePath);\n\n    // Image not in cache\n    Optional<LocalImage> localImage =\n        LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);\n    Assert.assertFalse(localImage.isPresent());\n\n    // Config in cache, but not layers\n    String configHash = \"066872f17ae819f846a6d5abcfc3165abe13fb0a157640fa8cb7af81077670c0\";\n    Files.copy(\n        getResource(\"core/extraction/test-cache/local/config/\" + configHash),\n        cachePath.resolve(\"local/config/\" + configHash));\n    localImage = LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);\n    Assert.assertFalse(localImage.isPresent());\n\n    // One layer missing\n    String diffId = \"5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd\";\n    String digest = \"0011328ac5dfe3dde40c7c5e0e00c98d1833a3aeae2bfb668cf9eb965c229c7f\";\n    Files.createDirectories(cachePath.resolve(\"local\").resolve(diffId));\n    Files.copy(\n        getResource(\"core/extraction/test-cache/local/\" + diffId + \"/\" + digest),\n        cachePath.resolve(\"local\").resolve(diffId).resolve(digest));\n    localImage = LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);\n    Assert.assertFalse(localImage.isPresent());\n\n    // Image fully in cache\n    diffId = \"f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e\";\n    digest = \"c10ef24a5cef5092bbcb5a5666721cff7b86ce978c203a958d1fc86ee6c19f94\";\n    Files.createDirectories(cachePath.resolve(\"local\").resolve(diffId));\n    Files.copy(\n        getResource(\"core/extraction/test-cache/local/\" + diffId + \"/\" + digest),\n        cachePath.resolve(\"local\").resolve(diffId).resolve(digest));\n    localImage = LocalBaseImageSteps.getCachedDockerImage(cache, dockerImageDetails);\n    Assert.assertTrue(localImage.isPresent());\n    LocalImage image = localImage.get();\n    Assert.assertEquals(2, image.configurationTemplate.getLayerCount());\n    Assert.assertEquals(2, image.layers.size());\n  }\n\n  @Test\n  public void testIsGzipped() throws URISyntaxException, IOException {\n    Assert.assertTrue(\n        LocalBaseImageSteps.isGzipped(getResource(\"core/extraction/compressed.tar.gz\")));\n    Assert.assertFalse(\n        LocalBaseImageSteps.isGzipped(getResource(\"core/extraction/not-compressed.tar\")));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/ObtainBaseImageLayerStepTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PreparedLayer.StateInTarget;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.image.ReferenceLayer;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.AdditionalAnswers;\nimport org.mockito.Answers;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\nimport org.mockito.stubbing.Answer3;\n\n/** Tests for {@link ObtainBaseImageLayerStep}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ObtainBaseImageLayerStepTest {\n\n  private DescriptorDigest existingLayerDigest;\n  private DescriptorDigest freshLayerDigest;\n\n  @Mock private Layer existingLayer;\n  @Mock private Layer freshLayer;\n  @Mock private RegistryClient registryClient;\n\n  @Mock(answer = Answers.RETURNS_MOCKS)\n  private BuildContext buildContext;\n\n  @Mock(answer = Answers.RETURNS_MOCKS)\n  private ProgressEventDispatcher.Factory progressDispatcherFactory;\n\n  @Before\n  public void setUp() throws IOException, RegistryException, DigestException {\n    existingLayerDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    freshLayerDigest =\n        DescriptorDigest.fromHash(\n            \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\");\n\n    DescriptorDigest diffId = Mockito.mock(DescriptorDigest.class);\n    existingLayer = new ReferenceLayer(new BlobDescriptor(existingLayerDigest), diffId);\n    freshLayer = new ReferenceLayer(new BlobDescriptor(freshLayerDigest), diffId);\n\n    Mockito.when(registryClient.checkBlob(existingLayerDigest))\n        .thenReturn(Optional.of(Mockito.mock(BlobDescriptor.class)));\n    Mockito.when(registryClient.checkBlob(freshLayerDigest)).thenReturn(Optional.empty());\n\n    // necessary to prevent error from classes dealing with progress report\n    Answer3<Blob, DescriptorDigest, Consumer<Long>, Consumer<Long>> progressSizeSetter =\n        (ignored1, progressSizeConsumer, ignored2) -> {\n          progressSizeConsumer.accept(Long.valueOf(12345));\n          return null;\n        };\n    Mockito.when(registryClient.pullBlob(Mockito.any(), Mockito.any(), Mockito.any()))\n        .thenAnswer(AdditionalAnswers.answer(progressSizeSetter));\n  }\n\n  @Test\n  public void testForSelectiveDownload_existingLayer()\n      throws IOException, CacheCorruptedException, RegistryException {\n    ObtainBaseImageLayerStep puller =\n        ObtainBaseImageLayerStep.forSelectiveDownload(\n            buildContext, progressDispatcherFactory, existingLayer, registryClient, registryClient);\n\n    PreparedLayer preparedLayer = puller.call();\n\n    Assert.assertEquals(StateInTarget.EXISTING, preparedLayer.getStateInTarget());\n    // Should have queried the blob.\n    Mockito.verify(registryClient).checkBlob(existingLayerDigest);\n    // The layer should not be pulled.\n    Mockito.verify(registryClient, Mockito.never())\n        .pullBlob(Mockito.eq(existingLayerDigest), Mockito.any(), Mockito.any());\n    Mockito.verifyNoMoreInteractions(registryClient);\n  }\n\n  @Test\n  public void testForSelectiveDownload_freshLayer()\n      throws IOException, CacheCorruptedException, RegistryException {\n    ObtainBaseImageLayerStep puller =\n        ObtainBaseImageLayerStep.forSelectiveDownload(\n            buildContext, progressDispatcherFactory, freshLayer, registryClient, registryClient);\n\n    PreparedLayer preparedLayer = puller.call();\n\n    Assert.assertEquals(StateInTarget.MISSING, preparedLayer.getStateInTarget());\n    // Should have queried the blob.\n    Mockito.verify(registryClient).checkBlob(freshLayerDigest);\n    // The layer should not be pulled.\n    Mockito.verify(registryClient)\n        .pullBlob(Mockito.eq(freshLayerDigest), Mockito.any(), Mockito.any());\n    Mockito.verifyNoMoreInteractions(registryClient);\n  }\n\n  @Test\n  public void testForForcedDownload_existingLayer()\n      throws IOException, CacheCorruptedException, RegistryException {\n    ObtainBaseImageLayerStep puller =\n        ObtainBaseImageLayerStep.forForcedDownload(\n            buildContext, progressDispatcherFactory, existingLayer, registryClient);\n    PreparedLayer preparedLayer = puller.call();\n\n    // existence unknown\n    Assert.assertEquals(StateInTarget.UNKNOWN, preparedLayer.getStateInTarget());\n    // No blob checking should happen.\n    Mockito.verify(registryClient, Mockito.never()).checkBlob(Mockito.any());\n    // The layer should be pulled.\n    Mockito.verify(registryClient)\n        .pullBlob(Mockito.eq(existingLayerDigest), Mockito.any(), Mockito.any());\n    Mockito.verifyNoMoreInteractions(registryClient);\n  }\n\n  @Test\n  public void testForForcedDownload_freshLayer()\n      throws IOException, CacheCorruptedException, RegistryException {\n    ObtainBaseImageLayerStep puller =\n        ObtainBaseImageLayerStep.forForcedDownload(\n            buildContext, progressDispatcherFactory, freshLayer, registryClient);\n    PreparedLayer preparedLayer = puller.call();\n\n    // existence unknown\n    Assert.assertEquals(StateInTarget.UNKNOWN, preparedLayer.getStateInTarget());\n    // No blob checking should happen.\n    Mockito.verify(registryClient, Mockito.never()).checkBlob(Mockito.any());\n    // The layer should be pulled.\n    Mockito.verify(registryClient)\n        .pullBlob(Mockito.eq(freshLayerDigest), Mockito.any(), Mockito.any());\n    Mockito.verifyNoMoreInteractions(registryClient);\n  }\n\n  @Test\n  public void testLayerMissingInCacheInOfflineMode()\n      throws CacheCorruptedException, RegistryException {\n    Mockito.when(buildContext.isOffline()).thenReturn(true);\n\n    ObtainBaseImageLayerStep puller =\n        ObtainBaseImageLayerStep.forForcedDownload(\n            buildContext, progressDispatcherFactory, freshLayer, registryClient);\n    try {\n      puller.call();\n      Assert.fail();\n    } catch (IOException ex) {\n      Assert.assertEquals(\n          \"Cannot run Jib in offline mode; local Jib cache for base image is missing image layer \"\n              + \"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb. Rerun \"\n              + \"Jib in online mode with \\\"-Djib.alwaysCacheBaseImage=true\\\" to re-download the \"\n              + \"base image layers.\",\n          ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException;\nimport com.google.common.collect.ImmutableSet;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link PlatformChecker}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class PlatformCheckerTest {\n\n  @Mock private BuildContext buildContext;\n  @Mock private ContainerConfiguration containerConfig;\n\n  @Before\n  public void setUp() {\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(ImageReference.scratch()).build());\n    Mockito.when(buildContext.getContainerConfiguration()).thenReturn(containerConfig);\n  }\n\n  @Test\n  public void testCheckManifestPlatform_mismatch() {\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"configured arch\", \"configured OS\")));\n\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n    containerConfigJson.setArchitecture(\"actual arch\");\n    containerConfigJson.setOs(\"actual OS\");\n    Exception ex =\n        assertThrows(\n            PlatformNotFoundInBaseImageException.class,\n            () -> PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson));\n    assertThat(ex)\n        .hasMessageThat()\n        .isEqualTo(\n            \"the configured platform (configured arch/configured OS) doesn't match the \"\n                + \"platform (actual arch/actual OS) of the base image (scratch)\");\n  }\n\n  @Test\n  public void testCheckManifestPlatform_noExceptionIfDefaultAmd64Linux()\n      throws PlatformNotFoundInBaseImageException {\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n    containerConfigJson.setArchitecture(\"actual arch\");\n    containerConfigJson.setOs(\"actual OS\");\n    PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson);\n  }\n\n  @Test\n  public void testCheckManifestPlatform_multiplePlatformsConfigured() {\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\"), new Platform(\"arch\", \"os\")));\n    Exception ex =\n        assertThrows(\n            PlatformNotFoundInBaseImageException.class,\n            () ->\n                PlatformChecker.checkManifestPlatform(\n                    buildContext, new ContainerConfigurationTemplate()));\n    assertThat(ex)\n        .hasMessageThat()\n        .isEqualTo(\n            \"cannot build for multiple platforms since the base image 'scratch' is not a manifest list.\");\n  }\n\n  @Test\n  public void testCheckManifestPlatform_tarBaseImage() {\n    Path tar = Paths.get(\"/foo/bar.tar\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(ImageReference.scratch()).setTarPath(tar).build());\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\"), new Platform(\"arch\", \"os\")));\n\n    Exception ex =\n        assertThrows(\n            PlatformNotFoundInBaseImageException.class,\n            () ->\n                PlatformChecker.checkManifestPlatform(\n                    buildContext, new ContainerConfigurationTemplate()));\n    assertThat(ex)\n        .hasMessageThat()\n        .isEqualTo(\n            \"cannot build for multiple platforms since the base image '\"\n                + tar\n                + \"' is not a manifest list.\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStepTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;\nimport com.google.cloud.tools.jib.cache.Cache;\nimport com.google.cloud.tools.jib.cache.CacheCorruptedException;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.LayerCountMismatchException;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.cloud.tools.jib.image.json.BadContainerConfigurationFormatException;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException;\nimport com.google.cloud.tools.jib.image.json.UnlistedPlatformInManifestListException;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.ManifestAndDigest;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link PullBaseImageStep}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class PullBaseImageStepTest {\n\n  @Mock private ProgressEventDispatcher.Factory progressDispatcherFactory;\n  @Mock private ProgressEventDispatcher progressDispatcher;\n  @Mock private BuildContext buildContext;\n  @Mock private RegistryClient registryClient;\n  @Mock private RegistryClient.Factory registryClientFactory;\n  @Mock private ImageConfiguration imageConfiguration;\n  @Mock private ContainerConfiguration containerConfig;\n  @Mock private Cache cache;\n  @Mock private EventHandlers eventHandlers;\n\n  private PullBaseImageStep pullBaseImageStep;\n\n  @Before\n  public void setUp() {\n    Mockito.when(buildContext.getBaseImageConfiguration()).thenReturn(imageConfiguration);\n    Mockito.when(buildContext.getEventHandlers()).thenReturn(eventHandlers);\n    Mockito.when(buildContext.getBaseImageLayersCache()).thenReturn(cache);\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory())\n        .thenReturn(registryClientFactory);\n    Mockito.when(registryClientFactory.newRegistryClient()).thenReturn(registryClient);\n    Mockito.when(buildContext.getContainerConfiguration()).thenReturn(containerConfig);\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"slim arch\", \"fat system\")));\n    Mockito.when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong()))\n        .thenReturn(progressDispatcher);\n    Mockito.when(progressDispatcher.newChildProducer()).thenReturn(progressDispatcherFactory);\n\n    pullBaseImageStep = new PullBaseImageStep(buildContext, progressDispatcherFactory);\n  }\n\n  @Test\n  public void testCall_scratch_singlePlatform()\n      throws LayerPropertyNotFoundException, IOException, RegistryException,\n          LayerCountMismatchException, BadContainerConfigurationFormatException,\n          CacheCorruptedException, CredentialRetrievalException {\n    Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.scratch());\n    ImagesAndRegistryClient result = pullBaseImageStep.call();\n\n    Assert.assertEquals(1, result.images.size());\n    Assert.assertEquals(\"slim arch\", result.images.get(0).getArchitecture());\n    Assert.assertEquals(\"fat system\", result.images.get(0).getOs());\n    Assert.assertNull(result.registryClient);\n  }\n\n  @Test\n  public void testCall_scratch_multiplePlatforms()\n      throws LayerPropertyNotFoundException, IOException, RegistryException,\n          LayerCountMismatchException, BadContainerConfigurationFormatException,\n          CacheCorruptedException, CredentialRetrievalException {\n    Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.scratch());\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(\n            ImmutableSet.of(\n                new Platform(\"architecture1\", \"os1\"), new Platform(\"architecture2\", \"os2\")));\n    ImagesAndRegistryClient result = pullBaseImageStep.call();\n\n    Assert.assertEquals(2, result.images.size());\n    Assert.assertEquals(\"architecture1\", result.images.get(0).getArchitecture());\n    Assert.assertEquals(\"os1\", result.images.get(0).getOs());\n    Assert.assertEquals(\"architecture2\", result.images.get(1).getArchitecture());\n    Assert.assertEquals(\"os2\", result.images.get(1).getOs());\n    Assert.assertNull(result.registryClient);\n  }\n\n  @Test\n  public void testCall_digestBaseImage()\n      throws LayerPropertyNotFoundException, IOException, RegistryException,\n          LayerCountMismatchException, BadContainerConfigurationFormatException,\n          CacheCorruptedException, CredentialRetrievalException, InvalidImageReferenceException {\n    ImageReference imageReference =\n        ImageReference.parse(\n            \"awesome@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    Assert.assertTrue(imageReference.getDigest().isPresent());\n    Mockito.when(imageConfiguration.getImage()).thenReturn(imageReference);\n\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n    containerConfigJson.setArchitecture(\"slim arch\");\n    containerConfigJson.setOs(\"fat system\");\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V22ManifestTemplate(), containerConfigJson, \"sha256:digest\");\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true);\n\n    ImagesAndRegistryClient result = pullBaseImageStep.call();\n    Assert.assertEquals(\"fat system\", result.images.get(0).getOs());\n    Assert.assertEquals(registryClient, result.registryClient);\n  }\n\n  @Test\n  public void testCall_offlineMode_notCached()\n      throws LayerPropertyNotFoundException, RegistryException, LayerCountMismatchException,\n          BadContainerConfigurationFormatException, CacheCorruptedException,\n          CredentialRetrievalException, InvalidImageReferenceException {\n    Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.parse(\"cat\"));\n    Mockito.when(buildContext.isOffline()).thenReturn(true);\n\n    try {\n      pullBaseImageStep.call();\n      Assert.fail();\n    } catch (IOException ex) {\n      Assert.assertEquals(\n          \"Cannot run Jib in offline mode; cat not found in local Jib cache\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCall_offlineMode_cached()\n      throws LayerPropertyNotFoundException, RegistryException, LayerCountMismatchException,\n          BadContainerConfigurationFormatException, CacheCorruptedException,\n          CredentialRetrievalException, InvalidImageReferenceException, IOException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(imageConfiguration.getImage()).thenReturn(imageReference);\n    Mockito.when(buildContext.isOffline()).thenReturn(true);\n\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n    containerConfigJson.setArchitecture(\"slim arch\");\n    containerConfigJson.setOs(\"fat system\");\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V22ManifestTemplate(), containerConfigJson, \"sha256:digest\");\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true);\n\n    ImagesAndRegistryClient result = pullBaseImageStep.call();\n    Assert.assertEquals(\"fat system\", result.images.get(0).getOs());\n    Assert.assertNull(result.registryClient);\n\n    Mockito.verify(buildContext, Mockito.never()).newBaseImageRegistryClientFactory();\n  }\n\n  @Test\n  public void testLookUpPlatformSpecificDockerImageManifest()\n      throws IOException, UnlistedPlatformInManifestListException {\n    String manifestListJson =\n        \" {\\n\"\n            + \"   \\\"schemaVersion\\\": 2,\\n\"\n            + \"   \\\"mediaType\\\": \\\"application/vnd.docker.distribution.manifest.list.v2+json\\\",\\n\"\n            + \"   \\\"manifests\\\": [\\n\"\n            + \"      {\\n\"\n            + \"         \\\"mediaType\\\": \\\"application/vnd.docker.distribution.manifest.v2+json\\\",\\n\"\n            + \"         \\\"size\\\": 424,\\n\"\n            + \"         \\\"digest\\\": \\\"sha256:1111111111111111111111111111111111111111111111111111111111111111\\\",\\n\"\n            + \"         \\\"platform\\\": {\\n\"\n            + \"            \\\"architecture\\\": \\\"arm64\\\",\\n\"\n            + \"            \\\"os\\\": \\\"linux\\\"\\n\"\n            + \"         }\\n\"\n            + \"      },\\n\"\n            + \"      {\\n\"\n            + \"         \\\"mediaType\\\": \\\"application/vnd.docker.distribution.manifest.v2+json\\\",\\n\"\n            + \"         \\\"size\\\": 425,\\n\"\n            + \"         \\\"digest\\\": \\\"sha256:2222222222222222222222222222222222222222222222222222222222222222\\\",\\n\"\n            + \"         \\\"platform\\\": {\\n\"\n            + \"            \\\"architecture\\\": \\\"targetArchitecture\\\",\\n\"\n            + \"            \\\"os\\\": \\\"targetOS\\\"\\n\"\n            + \"         }\\n\"\n            + \"      }\\n\"\n            + \"   ]\\n\"\n            + \"}\";\n\n    V22ManifestListTemplate manifestList =\n        JsonTemplateMapper.readJson(manifestListJson, V22ManifestListTemplate.class);\n\n    String manifestDigest =\n        pullBaseImageStep.lookUpPlatformSpecificImageManifest(\n            manifestList, new Platform(\"targetArchitecture\", \"targetOS\"));\n\n    Assert.assertEquals(\n        \"sha256:2222222222222222222222222222222222222222222222222222222222222222\", manifestDigest);\n  }\n\n  @Test\n  public void testLookUpPlatformSpecificOciManifest()\n      throws IOException, UnlistedPlatformInManifestListException {\n    String manifestListJson =\n        \" {\\n\"\n            + \"   \\\"schemaVersion\\\": 2,\\n\"\n            + \"   \\\"mediaType\\\": \\\"application/vnd.oci.image.index.v1+json\\\",\\n\"\n            + \"   \\\"manifests\\\": [\\n\"\n            + \"      {\\n\"\n            + \"         \\\"mediaType\\\": \\\"application/vnd.oci.image.manifest.v1+json\\\",\\n\"\n            + \"         \\\"size\\\": 424,\\n\"\n            + \"         \\\"digest\\\": \\\"sha256:1111111111111111111111111111111111111111111111111111111111111111\\\",\\n\"\n            + \"         \\\"platform\\\": {\\n\"\n            + \"            \\\"architecture\\\": \\\"arm64\\\",\\n\"\n            + \"            \\\"os\\\": \\\"linux\\\"\\n\"\n            + \"         }\\n\"\n            + \"      },\\n\"\n            + \"      {\\n\"\n            + \"         \\\"mediaType\\\": \\\"application/vnd.oci.image.manifest.v1+json\\\",\\n\"\n            + \"         \\\"size\\\": 425,\\n\"\n            + \"         \\\"digest\\\": \\\"sha256:2222222222222222222222222222222222222222222222222222222222222222\\\",\\n\"\n            + \"         \\\"platform\\\": {\\n\"\n            + \"            \\\"architecture\\\": \\\"targetArchitecture\\\",\\n\"\n            + \"            \\\"os\\\": \\\"targetOS\\\"\\n\"\n            + \"         }\\n\"\n            + \"      }\\n\"\n            + \"   ]\\n\"\n            + \"}\";\n\n    OciIndexTemplate manifestList =\n        JsonTemplateMapper.readJson(manifestListJson, OciIndexTemplate.class);\n\n    String manifestDigest =\n        pullBaseImageStep.lookUpPlatformSpecificImageManifest(\n            manifestList, new Platform(\"targetArchitecture\", \"targetOS\"));\n\n    Assert.assertEquals(\n        \"sha256:2222222222222222222222222222222222222222222222222222222222222222\", manifestDigest);\n  }\n\n  @Test\n  public void testGetCachedBaseImages_emptyCache()\n      throws InvalidImageReferenceException, IOException, CacheCorruptedException,\n          UnlistedPlatformInManifestListException, PlatformNotFoundInBaseImageException,\n          BadContainerConfigurationFormatException, LayerCountMismatchException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.empty());\n\n    Assert.assertEquals(Arrays.asList(), pullBaseImageStep.getCachedBaseImages());\n  }\n\n  @Test\n  public void testGetCachedBaseImages_partiallyCached_emptyListReturned()\n      throws InvalidImageReferenceException, CacheCorruptedException, IOException,\n          LayerCountMismatchException, PlatformNotFoundInBaseImageException,\n          BadContainerConfigurationFormatException, UnlistedPlatformInManifestListException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n    ManifestTemplate manifest = Mockito.mock(ManifestTemplate.class);\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(\n            null, Arrays.asList(new ManifestAndConfigTemplate(manifest, null)));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(cache.areAllLayersCached(manifest)).thenReturn(false);\n\n    assertThat(pullBaseImageStep.getCachedBaseImages()).isEmpty();\n  }\n\n  @Test\n  public void testGetCachedBaseImages_v21ManifestCached()\n      throws InvalidImageReferenceException, IOException, CacheCorruptedException,\n          UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException,\n          LayerCountMismatchException, DigestException, PlatformNotFoundInBaseImageException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n\n    DescriptorDigest layerDigest =\n        DescriptorDigest.fromHash(\n            \"1111111111111111111111111111111111111111111111111111111111111111\");\n    V21ManifestTemplate v21Manifest = Mockito.mock(V21ManifestTemplate.class);\n    Mockito.when(v21Manifest.getLayerDigests()).thenReturn(Arrays.asList(layerDigest));\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(\n            null, Arrays.asList(new ManifestAndConfigTemplate(v21Manifest, null)));\n\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(cache.areAllLayersCached(v21Manifest)).thenReturn(true);\n\n    List<Image> images = pullBaseImageStep.getCachedBaseImages();\n\n    Assert.assertEquals(1, images.size());\n    Assert.assertEquals(1, images.get(0).getLayers().size());\n    Assert.assertEquals(\n        \"1111111111111111111111111111111111111111111111111111111111111111\",\n        images.get(0).getLayers().get(0).getBlobDescriptor().getDigest().getHash());\n  }\n\n  @Test\n  public void testGetCachedBaseImages_manifestCached()\n      throws InvalidImageReferenceException, IOException, CacheCorruptedException,\n          UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException,\n          LayerCountMismatchException, PlatformNotFoundInBaseImageException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n    containerConfigJson.setArchitecture(\"slim arch\");\n    containerConfigJson.setOs(\"fat system\");\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            Mockito.mock(BuildableManifestTemplate.class), containerConfigJson, \"sha256:digest\");\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true);\n\n    List<Image> images = pullBaseImageStep.getCachedBaseImages();\n\n    Assert.assertEquals(1, images.size());\n    Assert.assertEquals(\"slim arch\", images.get(0).getArchitecture());\n    Assert.assertEquals(\"fat system\", images.get(0).getOs());\n  }\n\n  @Test\n  public void testGetCachedBaseImages_manifestListCached()\n      throws InvalidImageReferenceException, IOException, CacheCorruptedException,\n          UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException,\n          LayerCountMismatchException, PlatformNotFoundInBaseImageException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n\n    ContainerConfigurationTemplate containerConfigJson1 = new ContainerConfigurationTemplate();\n    ContainerConfigurationTemplate containerConfigJson2 = new ContainerConfigurationTemplate();\n    containerConfigJson1.setContainerUser(\"user1\");\n    containerConfigJson2.setContainerUser(\"user2\");\n\n    ManifestListTemplate manifestList = Mockito.mock(ManifestListTemplate.class);\n    Mockito.when(manifestList.getDigestsForPlatform(\"arch1\", \"os1\"))\n        .thenReturn(Arrays.asList(\"sha256:digest1\"));\n    Mockito.when(manifestList.getDigestsForPlatform(\"arch2\", \"os2\"))\n        .thenReturn(Arrays.asList(\"sha256:digest2\"));\n\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(\n            manifestList,\n            Arrays.asList(\n                new ManifestAndConfigTemplate(\n                    Mockito.mock(BuildableManifestTemplate.class),\n                    containerConfigJson1,\n                    \"sha256:digest1\"),\n                new ManifestAndConfigTemplate(\n                    Mockito.mock(BuildableManifestTemplate.class),\n                    containerConfigJson2,\n                    \"sha256:digest2\")));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(\n            cache.areAllLayersCached(imageMetadata.getManifestsAndConfigs().get(0).getManifest()))\n        .thenReturn(true);\n    Mockito.when(\n            cache.areAllLayersCached(imageMetadata.getManifestsAndConfigs().get(1).getManifest()))\n        .thenReturn(true);\n\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"arch1\", \"os1\"), new Platform(\"arch2\", \"os2\")));\n\n    List<Image> images = pullBaseImageStep.getCachedBaseImages();\n\n    Assert.assertEquals(2, images.size());\n    Assert.assertEquals(\"user1\", images.get(0).getUser());\n    Assert.assertEquals(\"user2\", images.get(1).getUser());\n  }\n\n  @Test\n  public void testGetCachedBaseImages_manifestListCached_partialMatches()\n      throws InvalidImageReferenceException, IOException, CacheCorruptedException,\n          UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException,\n          LayerCountMismatchException, PlatformNotFoundInBaseImageException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n\n    ManifestListTemplate manifestList = Mockito.mock(ManifestListTemplate.class);\n    Mockito.when(manifestList.getDigestsForPlatform(\"arch1\", \"os1\"))\n        .thenReturn(Arrays.asList(\"sha256:digest1\"));\n    Mockito.when(manifestList.getDigestsForPlatform(\"arch2\", \"os2\"))\n        .thenReturn(Arrays.asList(\"sha256:digest2\"));\n\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(\n            manifestList,\n            Arrays.asList(\n                new ManifestAndConfigTemplate(\n                    Mockito.mock(BuildableManifestTemplate.class),\n                    new ContainerConfigurationTemplate(),\n                    \"sha256:digest1\")));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(\n            cache.areAllLayersCached(imageMetadata.getManifestsAndConfigs().get(0).getManifest()))\n        .thenReturn(true);\n\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"arch1\", \"os1\"), new Platform(\"arch2\", \"os2\")));\n\n    Assert.assertEquals(Arrays.asList(), pullBaseImageStep.getCachedBaseImages());\n  }\n\n  @Test\n  public void testGetCachedBaseImages_manifestListCached_onlyPlatforms()\n      throws InvalidImageReferenceException, IOException, CacheCorruptedException,\n          UnlistedPlatformInManifestListException, PlatformNotFoundInBaseImageException,\n          BadContainerConfigurationFormatException, LayerCountMismatchException {\n    ImageReference imageReference = ImageReference.parse(\"cat\");\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(imageReference).build());\n\n    ManifestListTemplate manifestList = Mockito.mock(ManifestListTemplate.class);\n    Mockito.when(manifestList.getDigestsForPlatform(\"target-arch\", \"target-os\"))\n        .thenReturn(Arrays.asList(\"sha256:target-digest\"));\n\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n    containerConfigJson.setContainerUser(\"target-user\");\n\n    ManifestAndConfigTemplate targetManifestAndConfig =\n        new ManifestAndConfigTemplate(\n            Mockito.mock(BuildableManifestTemplate.class),\n            containerConfigJson,\n            \"sha256:target-digest\");\n    ManifestAndConfigTemplate unrelatedManifestAndConfig =\n        new ManifestAndConfigTemplate(\n            Mockito.mock(BuildableManifestTemplate.class),\n            new ContainerConfigurationTemplate(),\n            \"sha256:unrelated-digest\");\n\n    ImageMetadataTemplate imageMetadata =\n        new ImageMetadataTemplate(\n            manifestList,\n            Arrays.asList(\n                unrelatedManifestAndConfig, targetManifestAndConfig, unrelatedManifestAndConfig));\n    Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata));\n    Mockito.when(cache.areAllLayersCached(targetManifestAndConfig.getManifest())).thenReturn(true);\n\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"target-arch\", \"target-os\")));\n\n    List<Image> images = pullBaseImageStep.getCachedBaseImages();\n\n    Assert.assertEquals(1, images.size());\n    Assert.assertEquals(\"target-user\", images.get(0).getUser());\n  }\n\n  @Test\n  public void testTryMirrors_noMatchingMirrors()\n      throws LayerCountMismatchException, BadContainerConfigurationFormatException,\n          PlatformNotFoundInBaseImageException {\n    Mockito.when(imageConfiguration.getImageRegistry()).thenReturn(\"registry\");\n    Mockito.when(buildContext.getRegistryMirrors())\n        .thenReturn(ImmutableListMultimap.of(\"unmatched1\", \"mirror1\", \"unmatched2\", \"mirror2\"));\n\n    Optional<ImagesAndRegistryClient> result =\n        pullBaseImageStep.tryMirrors(buildContext, progressDispatcherFactory);\n    Assert.assertEquals(Optional.empty(), result);\n\n    InOrder inOrder = Mockito.inOrder(eventHandlers);\n    inOrder.verify(eventHandlers).dispatch(LogEvent.debug(\"mirror config: unmatched1 --> mirror1\"));\n    inOrder.verify(eventHandlers).dispatch(LogEvent.debug(\"mirror config: unmatched2 --> mirror2\"));\n    Mockito.verify(buildContext, Mockito.never()).newBaseImageRegistryClientFactory(Mockito.any());\n  }\n\n  @Test\n  public void testTryMirrors_mirrorIoError()\n      throws LayerCountMismatchException, BadContainerConfigurationFormatException, IOException,\n          RegistryException {\n    Mockito.when(imageConfiguration.getImageRegistry()).thenReturn(\"registry\");\n    Mockito.when(buildContext.getRegistryMirrors())\n        .thenReturn(ImmutableListMultimap.of(\"registry\", \"gcr.io\"));\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory(\"gcr.io\"))\n        .thenReturn(registryClientFactory);\n    Mockito.when(registryClient.pullManifest(Mockito.any()))\n        .thenThrow(new IOException(\"test exception\"));\n\n    Optional<ImagesAndRegistryClient> result =\n        pullBaseImageStep.tryMirrors(buildContext, progressDispatcherFactory);\n    Assert.assertEquals(Optional.empty(), result);\n\n    InOrder inOrder = Mockito.inOrder(eventHandlers);\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.info(\"trying mirror gcr.io for the base image\"));\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.debug(\"failed to get manifest from mirror gcr.io: test exception\"));\n  }\n\n  @Test\n  public void testTryMirrors_multipleMirrors()\n      throws LayerCountMismatchException, BadContainerConfigurationFormatException, IOException,\n          RegistryException, InvalidImageReferenceException {\n    Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.parse(\"registry/repo\"));\n    Mockito.when(imageConfiguration.getImageRegistry()).thenReturn(\"registry\");\n    Mockito.when(buildContext.getRegistryMirrors())\n        .thenReturn(ImmutableListMultimap.of(\"registry\", \"quay.io\", \"registry\", \"gcr.io\"));\n\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory(\"quay.io\"))\n        .thenReturn(registryClientFactory);\n    Mockito.when(registryClient.pullManifest(Mockito.any()))\n        .thenThrow(new RegistryException(\"not found\"));\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n\n    RegistryClient.Factory gcrRegistryClientFactory =\n        setUpWorkingRegistryClientFactoryWithV22ManifestTemplate();\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory(\"gcr.io\"))\n        .thenReturn(gcrRegistryClientFactory);\n\n    Optional<ImagesAndRegistryClient> result =\n        pullBaseImageStep.tryMirrors(buildContext, progressDispatcherFactory);\n    Assert.assertTrue(result.isPresent());\n    Assert.assertEquals(gcrRegistryClientFactory.newRegistryClient(), result.get().registryClient);\n\n    InOrder inOrder = Mockito.inOrder(eventHandlers);\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.info(\"trying mirror quay.io for the base image\"));\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.debug(\"failed to get manifest from mirror quay.io: not found\"));\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.info(\"trying mirror gcr.io for the base image\"));\n    inOrder.verify(eventHandlers).dispatch(LogEvent.info(\"pulled manifest from mirror gcr.io\"));\n  }\n\n  @Test\n  public void testCall_allMirrorsFail()\n      throws InvalidImageReferenceException, IOException, RegistryException,\n          LayerPropertyNotFoundException, LayerCountMismatchException,\n          BadContainerConfigurationFormatException, CacheCorruptedException,\n          CredentialRetrievalException {\n    Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.parse(\"registry/repo\"));\n    Mockito.when(imageConfiguration.getImageRegistry()).thenReturn(\"registry\");\n    Mockito.when(buildContext.getRegistryMirrors())\n        .thenReturn(ImmutableListMultimap.of(\"registry\", \"quay.io\", \"registry\", \"gcr.io\"));\n\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory(Mockito.any()))\n        .thenReturn(registryClientFactory);\n    Mockito.when(registryClient.pullManifest(Mockito.any()))\n        .thenThrow(new RegistryException(\"not found\"));\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n    RegistryClient.Factory dockerHubRegistryClientFactory =\n        setUpWorkingRegistryClientFactoryWithV22ManifestTemplate();\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory())\n        .thenReturn(dockerHubRegistryClientFactory);\n\n    ImagesAndRegistryClient result = pullBaseImageStep.call();\n    Assert.assertEquals(dockerHubRegistryClientFactory.newRegistryClient(), result.registryClient);\n\n    InOrder inOrder = Mockito.inOrder(eventHandlers);\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.info(\"trying mirror quay.io for the base image\"));\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.debug(\"failed to get manifest from mirror quay.io: not found\"));\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.info(\"trying mirror gcr.io for the base image\"));\n    inOrder\n        .verify(eventHandlers)\n        .dispatch(LogEvent.debug(\"failed to get manifest from mirror gcr.io: not found\"));\n  }\n\n  @Test\n  public void testCall_ManifestList()\n      throws InvalidImageReferenceException, IOException, RegistryException,\n          LayerPropertyNotFoundException, LayerCountMismatchException,\n          BadContainerConfigurationFormatException, CacheCorruptedException,\n          CredentialRetrievalException {\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(ImageReference.parse(\"multiarch\")).build());\n    Mockito.when(buildContext.getRegistryMirrors())\n        .thenReturn(ImmutableListMultimap.of(\"registry\", \"gcr.io\"));\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n    RegistryClient.Factory dockerHubRegistryClientFactory =\n        setUpWorkingRegistryClientFactoryWithV22ManifestList();\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory())\n        .thenReturn(dockerHubRegistryClientFactory);\n\n    ImagesAndRegistryClient result = pullBaseImageStep.call();\n    Assert.assertEquals(V22ManifestTemplate.class, result.images.get(0).getImageFormat());\n    Assert.assertEquals(\"linux\", result.images.get(0).getOs());\n    Assert.assertEquals(\"amd64\", result.images.get(0).getArchitecture());\n  }\n\n  @Test(expected = UnlistedPlatformInManifestListException.class)\n  public void testCall_ManifestList_UnknownArchitecture()\n      throws InvalidImageReferenceException, IOException, RegistryException,\n          LayerPropertyNotFoundException, LayerCountMismatchException,\n          BadContainerConfigurationFormatException, CacheCorruptedException,\n          CredentialRetrievalException {\n    Mockito.when(buildContext.getBaseImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(ImageReference.parse(\"multiarch\")).build());\n    Mockito.when(buildContext.getRegistryMirrors())\n        .thenReturn(ImmutableListMultimap.of(\"registry\", \"gcr.io\"));\n    Mockito.when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"arm64\", \"linux\")));\n    RegistryClient.Factory dockerHubRegistryClientFactory =\n        setUpWorkingRegistryClientFactoryWithV22ManifestList();\n    Mockito.when(buildContext.newBaseImageRegistryClientFactory())\n        .thenReturn(dockerHubRegistryClientFactory);\n\n    pullBaseImageStep.call();\n  }\n\n  private static RegistryClient.Factory setUpWorkingRegistryClientFactoryWithV22ManifestTemplate()\n      throws IOException, RegistryException {\n    DescriptorDigest digest = Mockito.mock(DescriptorDigest.class);\n    V22ManifestTemplate manifest = new V22ManifestTemplate();\n    manifest.setContainerConfiguration(1234, digest);\n\n    RegistryClient.Factory clientFactory = Mockito.mock(RegistryClient.Factory.class);\n    RegistryClient client = Mockito.mock(RegistryClient.class);\n    Mockito.when(clientFactory.newRegistryClient()).thenReturn(client);\n    Mockito.when(client.pullManifest(Mockito.any()))\n        .thenReturn(new ManifestAndDigest<>(manifest, digest));\n    // mocking pulling container config json\n    Mockito.when(client.pullBlob(Mockito.any(), Mockito.any(), Mockito.any()))\n        .then(\n            invocation -> {\n              Consumer<Long> blobSizeListener = invocation.getArgument(1);\n              blobSizeListener.accept(1L);\n              return Blobs.from(\"{}\");\n            });\n    return clientFactory;\n  }\n\n  private static RegistryClient.Factory setUpWorkingRegistryClientFactoryWithV22ManifestList()\n      throws IOException, RegistryException {\n    DescriptorDigest digest = Mockito.mock(DescriptorDigest.class);\n    V22ManifestListTemplate manifestList = new V22ManifestListTemplate();\n    V22ManifestListTemplate.ManifestDescriptorTemplate platformManifest =\n        new V22ManifestListTemplate.ManifestDescriptorTemplate();\n    platformManifest.setMediaType(V22ManifestTemplate.MANIFEST_MEDIA_TYPE);\n    platformManifest.setSize(1234);\n    platformManifest.setDigest(\"sha256:aaaaaaa\");\n    platformManifest.setPlatform(\"amd64\", \"linux\");\n    manifestList.addManifest(platformManifest);\n\n    V22ManifestTemplate manifest = new V22ManifestTemplate();\n    manifest.setContainerConfiguration(1234, digest);\n\n    RegistryClient.Factory clientFactory = Mockito.mock(RegistryClient.Factory.class);\n    RegistryClient client = Mockito.mock(RegistryClient.class);\n    Mockito.when(clientFactory.newRegistryClient()).thenReturn(client);\n    Mockito.when(client.pullManifest(eq(\"sha256:aaaaaaa\")))\n        .thenReturn(new ManifestAndDigest<>(manifest, digest));\n    Mockito.when(client.pullManifest(eq(\"latest\")))\n        .thenReturn(new ManifestAndDigest<>(manifestList, digest));\n    // mocking pulling container config json\n    Mockito.when(client.pullBlob(Mockito.any(), Mockito.any(), Mockito.any()))\n        .then(\n            invocation -> {\n              Consumer<Long> blobSizeListener = invocation.getArgument(1);\n              blobSizeListener.accept(1L);\n              return Blobs.from(\"{}\");\n            });\n    return clientFactory;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PushBlobStepTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport java.io.IOException;\nimport java.util.Optional;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Answers;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link PushBlobStep}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class PushBlobStepTest {\n\n  @Mock private BlobDescriptor blobDescriptor;\n  @Mock private RegistryClient registryClient;\n\n  @Mock(answer = Answers.RETURNS_MOCKS)\n  private ProgressEventDispatcher.Factory progressDispatcherFactory;\n\n  @Mock(answer = Answers.RETURNS_MOCKS)\n  private BuildContext buildContext;\n\n  @Before\n  public void setUp() {\n    Mockito.when(buildContext.getTargetImageConfiguration())\n        .thenReturn(ImageConfiguration.builder(ImageReference.scratch()).build());\n  }\n\n  @Test\n  public void testCall_doBlobCheckAndBlobExists() throws IOException, RegistryException {\n    Mockito.when(registryClient.checkBlob(Mockito.any())).thenReturn(Optional.of(blobDescriptor));\n\n    call(false);\n\n    Mockito.verify(registryClient).checkBlob(Mockito.any());\n    Mockito.verify(registryClient, Mockito.never())\n        .pushBlob(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());\n  }\n\n  @Test\n  public void testCall_doBlobCheckAndBlobDoesNotExist() throws IOException, RegistryException {\n    Mockito.when(registryClient.checkBlob(Mockito.any())).thenReturn(Optional.empty());\n\n    call(false);\n\n    Mockito.verify(registryClient).checkBlob(Mockito.any());\n    Mockito.verify(registryClient)\n        .pushBlob(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());\n  }\n\n  @Test\n  public void testCall_forcePushWithNoBlobCheck() throws IOException, RegistryException {\n    call(true);\n\n    Mockito.verify(registryClient, Mockito.never()).checkBlob(Mockito.any());\n    Mockito.verify(registryClient)\n        .pushBlob(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());\n  }\n\n  private void call(boolean forcePush) throws IOException, RegistryException {\n    new PushBlobStep(\n            buildContext,\n            progressDispatcherFactory,\n            registryClient,\n            blobDescriptor,\n            null,\n            forcePush)\n        .call();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PushImageStepTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.util.List;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link PushImageStep}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class PushImageStepTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  @Mock private ProgressEventDispatcher.Factory progressDispatcherFactory;\n  @Mock private ProgressEventDispatcher progressDispatcher;\n  @Mock private BuildContext buildContext;\n  @Mock private RegistryClient registryClient;\n  @Mock private ContainerConfiguration containerConfig;\n  @Mock private DescriptorDigest mockDescriptorDigest;\n\n  private final V22ManifestListTemplate manifestList = new V22ManifestListTemplate();\n\n  @Before\n  public void setUp() {\n    when(buildContext.getAllTargetImageTags()).thenReturn(ImmutableSet.of(\"tag1\", \"tag2\"));\n    when(buildContext.getEventHandlers()).thenReturn(EventHandlers.NONE);\n    when(buildContext.getContainerConfiguration()).thenReturn(containerConfig);\n    doReturn(V22ManifestTemplate.class).when(buildContext).getTargetFormat();\n    when(containerConfig.getPlatforms())\n        .thenReturn(\n            ImmutableSet.of(new Platform(\"amd64\", \"linux\"), new Platform(\"arm64\", \"windows\")));\n    when(progressDispatcherFactory.create(anyString(), anyLong())).thenReturn(progressDispatcher);\n    when(progressDispatcher.newChildProducer()).thenReturn(progressDispatcherFactory);\n\n    ManifestDescriptorTemplate manifest = new ManifestDescriptorTemplate();\n    manifest.setSize(100);\n    manifest.setDigest(\"sha256:1f25787aab4669d252bdae09a72b9c345d2a7b8c64c8dbfba4c82af4834dbccc\");\n    manifestList.addManifest(manifest);\n  }\n\n  @Test\n  public void testMakeListForManifestList() throws IOException, RegistryException {\n    List<PushImageStep> pushImageStepList =\n        PushImageStep.makeListForManifestList(\n            buildContext, progressDispatcherFactory, registryClient, manifestList, false);\n\n    assertThat(pushImageStepList).hasSize(2);\n    for (PushImageStep pushImageStep : pushImageStepList) {\n      BuildResult buildResult = pushImageStep.call();\n      assertThat(buildResult.getImageDigest().toString())\n          .isEqualTo(\"sha256:64303e82b8a80ef20475dc7f807b81f172cacce1a59191927f3a7ea5222f38ae\");\n      assertThat(buildResult.getImageId().toString())\n          .isEqualTo(\"sha256:64303e82b8a80ef20475dc7f807b81f172cacce1a59191927f3a7ea5222f38ae\");\n    }\n  }\n\n  @Test\n  public void testMakeList_multiPlatform_platformTags() throws IOException, RegistryException {\n    Image image = Image.builder(V22ManifestTemplate.class).setArchitecture(\"wasm\").build();\n\n    when(buildContext.getEnablePlatformTags()).thenReturn(true);\n\n    List<PushImageStep> pushImageStepList =\n        PushImageStep.makeList(\n            buildContext,\n            progressDispatcherFactory,\n            registryClient,\n            new BlobDescriptor(mockDescriptorDigest),\n            image,\n            false);\n\n    ArgumentCaptor<String> tagCatcher = ArgumentCaptor.forClass(String.class);\n    when(registryClient.pushManifest(any(), tagCatcher.capture())).thenReturn(null);\n\n    assertThat(pushImageStepList).hasSize(2);\n    pushImageStepList.get(0).call();\n    pushImageStepList.get(1).call();\n\n    assertThat(tagCatcher.getAllValues()).containsExactly(\"tag1-wasm\", \"tag2-wasm\");\n  }\n\n  @Test\n  public void testMakeList_multiPlatform_nonPlatformTags() throws IOException, RegistryException {\n    Image image = Image.builder(V22ManifestTemplate.class).setArchitecture(\"wasm\").build();\n    when(buildContext.getEnablePlatformTags()).thenReturn(false);\n\n    List<PushImageStep> pushImageStepList =\n        PushImageStep.makeList(\n            buildContext,\n            progressDispatcherFactory,\n            registryClient,\n            new BlobDescriptor(mockDescriptorDigest),\n            image,\n            false);\n\n    ArgumentCaptor<String> tagCatcher = ArgumentCaptor.forClass(String.class);\n    when(registryClient.pushManifest(any(), tagCatcher.capture())).thenReturn(null);\n\n    assertThat(pushImageStepList).hasSize(1);\n    pushImageStepList.get(0).call();\n    assertThat(tagCatcher.getAllValues())\n        .containsExactly(\"sha256:0dd75658cf52608fbd72eb95ff5fc5946966258c3676b35d336bfcc7ac5006f1\");\n  }\n\n  @Test\n  public void testMakeListForManifestList_singlePlatform() throws IOException {\n    when(containerConfig.getPlatforms())\n        .thenReturn(ImmutableSet.of(new Platform(\"amd64\", \"linux\")));\n\n    List<PushImageStep> pushImageStepList =\n        PushImageStep.makeListForManifestList(\n            buildContext, progressDispatcherFactory, registryClient, manifestList, false);\n    assertThat(pushImageStepList).isEmpty();\n  }\n\n  @Test\n  public void testMakeListForManifestList_manifestListAlreadyExists() throws IOException {\n    System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, \"true\");\n\n    List<PushImageStep> pushImageStepList =\n        PushImageStep.makeListForManifestList(\n            buildContext, progressDispatcherFactory, registryClient, manifestList, true);\n    assertThat(pushImageStepList).isEmpty();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/RegistryCredentialRetrieverTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.configuration.ContainerConfiguration;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link RegistryCredentialRetriever}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class RegistryCredentialRetrieverTest {\n\n  @Mock private EventHandlers mockEventHandlers;\n\n  @Test\n  public void testCall_retrieved()\n      throws CredentialRetrievalException, CacheDirectoryCreationException {\n    BuildContext buildContext =\n        makeFakeBuildContext(\n            Arrays.asList(\n                Optional::empty,\n                () -> Optional.of(Credential.from(\"baseusername\", \"basepassword\"))),\n            Arrays.asList(\n                () -> Optional.of(Credential.from(\"targetusername\", \"targetpassword\")),\n                () -> Optional.of(Credential.from(\"ignored\", \"ignored\"))));\n\n    Assert.assertEquals(\n        Optional.of(Credential.from(\"baseusername\", \"basepassword\")),\n        RegistryCredentialRetriever.getBaseImageCredential(buildContext));\n    Assert.assertEquals(\n        Optional.of(Credential.from(\"targetusername\", \"targetpassword\")),\n        RegistryCredentialRetriever.getTargetImageCredential(buildContext));\n  }\n\n  @Test\n  public void testCall_none() throws CredentialRetrievalException, CacheDirectoryCreationException {\n    BuildContext buildContext =\n        makeFakeBuildContext(\n            Arrays.asList(Optional::empty, Optional::empty), Collections.emptyList());\n    Assert.assertFalse(\n        RegistryCredentialRetriever.getBaseImageCredential(buildContext).isPresent());\n\n    Mockito.verify(mockEventHandlers)\n        .dispatch(LogEvent.info(\"No credentials could be retrieved for baseregistry/baserepo\"));\n\n    Assert.assertFalse(\n        RegistryCredentialRetriever.getTargetImageCredential(buildContext).isPresent());\n\n    Mockito.verify(mockEventHandlers)\n        .dispatch(LogEvent.info(\"No credentials could be retrieved for targetregistry/targetrepo\"));\n  }\n\n  @Test\n  public void testCall_exception() throws CacheDirectoryCreationException {\n    CredentialRetrievalException credentialRetrievalException =\n        Mockito.mock(CredentialRetrievalException.class);\n    BuildContext buildContext =\n        makeFakeBuildContext(\n            Collections.singletonList(\n                () -> {\n                  throw credentialRetrievalException;\n                }),\n            Collections.emptyList());\n    try {\n      RegistryCredentialRetriever.getBaseImageCredential(buildContext);\n      Assert.fail(\"Should have thrown exception\");\n\n    } catch (CredentialRetrievalException ex) {\n      Assert.assertSame(credentialRetrievalException, ex);\n    }\n  }\n\n  private BuildContext makeFakeBuildContext(\n      List<CredentialRetriever> baseCredentialRetrievers,\n      List<CredentialRetriever> targetCredentialRetrievers)\n      throws CacheDirectoryCreationException {\n    ImageReference baseImage = ImageReference.of(\"baseregistry\", \"baserepo\", null);\n    ImageReference targetImage = ImageReference.of(\"targetregistry\", \"targetrepo\", null);\n    return BuildContext.builder()\n        .setEventHandlers(mockEventHandlers)\n        .setBaseImageConfiguration(\n            ImageConfiguration.builder(baseImage)\n                .setCredentialRetrievers(baseCredentialRetrievers)\n                .build())\n        .setTargetImageConfiguration(\n            ImageConfiguration.builder(targetImage)\n                .setCredentialRetrievers(targetCredentialRetrievers)\n                .build())\n        .setContainerConfiguration(ContainerConfiguration.builder().build())\n        .setBaseImageLayersCacheDirectory(Paths.get(\"ignored\"))\n        .setApplicationLayersCacheDirectory(Paths.get(\"ignored\"))\n        .setExecutorService(MoreExecutors.newDirectExecutorService())\n        .build();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.builder.steps;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.builder.ProgressEventDispatcher;\nimport com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.image.DigestOnlyLayer;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.registry.ManifestAndDigest;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.util.concurrent.ForwardingExecutorService;\nimport com.google.common.util.concurrent.Futures;\nimport com.google.common.util.concurrent.ListenableFuture;\nimport com.google.common.util.concurrent.ListeningExecutorService;\nimport java.security.DigestException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link StepsRunner}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class StepsRunnerTest {\n\n  // ListeningExecutorService is annotated with @DoNotMock, so define a concrete class.\n  private class MockListeningExecutorService extends ForwardingExecutorService\n      implements ListeningExecutorService {\n\n    @Override\n    public <T> ListenableFuture<T> submit(Callable<T> task) {\n      try {\n        return Futures.immediateFuture(executorService.submit(task).get());\n      } catch (InterruptedException | ExecutionException ex) {\n        throw new IllegalStateException(ex);\n      }\n    }\n\n    @Override\n    public ListenableFuture<?> submit(Runnable task) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public <T> ListenableFuture<T> submit(Runnable task, T result) {\n      throw new UnsupportedOperationException();\n    }\n\n    @Override\n    protected ExecutorService delegate() {\n      throw new UnsupportedOperationException();\n    }\n  }\n\n  @Mock private BuildContext buildContext;\n  @Mock private EventHandlers eventHandlers;\n  @Mock private ProgressEventDispatcher.Factory progressDispatcherFactory;\n  @Mock private ProgressEventDispatcher progressDispatcher;\n  @Mock private ExecutorService executorService;\n  @Mock private Image builtArm64AndLinuxImage;\n  @Mock private Image builtAmd64AndWindowsImage;\n  @Mock private Image baseImage1;\n  @Mock private Image baseImage2;\n\n  private StepsRunner stepsRunner;\n\n  @Before\n  public void setup() {\n    stepsRunner = new StepsRunner(new MockListeningExecutorService(), buildContext);\n\n    when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong()))\n        .thenReturn(progressDispatcher);\n  }\n\n  @Test\n  public void testObtainBaseImageLayers_skipObtainingDuplicateLayers()\n      throws DigestException, InterruptedException, ExecutionException {\n    when(executorService.submit(Mockito.any(PullBaseImageStep.class)))\n        .thenReturn(Futures.immediateFuture(new ImagesAndRegistryClient(null, null)));\n    // Pretend that a thread pulling base images returned some (meaningless) result.\n    stepsRunner.pullBaseImages(progressDispatcherFactory);\n\n    DescriptorDigest digest1 =\n        DescriptorDigest.fromHash(\n            \"1111111111111111111111111111111111111111111111111111111111111111\");\n    DescriptorDigest digest2 =\n        DescriptorDigest.fromHash(\n            \"2222222222222222222222222222222222222222222222222222222222222222\");\n    DescriptorDigest digest3 =\n        DescriptorDigest.fromHash(\n            \"3333333333333333333333333333333333333333333333333333333333333333\");\n    DigestOnlyLayer layer1 = new DigestOnlyLayer(digest1);\n    DigestOnlyLayer layer2 = new DigestOnlyLayer(digest2);\n    DigestOnlyLayer layer3 = new DigestOnlyLayer(digest3);\n\n    PreparedLayer preparedLayer1 = Mockito.mock(PreparedLayer.class);\n    PreparedLayer preparedLayer2 = Mockito.mock(PreparedLayer.class);\n    PreparedLayer preparedLayer3 = Mockito.mock(PreparedLayer.class);\n    when(executorService.submit(Mockito.any(ObtainBaseImageLayerStep.class)))\n        .thenReturn(Futures.immediateFuture(preparedLayer1))\n        .thenReturn(Futures.immediateFuture(preparedLayer2))\n        .thenReturn(Futures.immediateFuture(preparedLayer3));\n\n    Map<DescriptorDigest, Future<PreparedLayer>> preparedLayersCache = new HashMap<>();\n\n    // 1. Should schedule two threads to obtain new layers.\n    Image image = Mockito.mock(Image.class);\n    when(image.getLayers()).thenReturn(ImmutableList.of(layer1, layer2));\n\n    stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory);\n    Assert.assertEquals(2, preparedLayersCache.size()); // two new layers cached\n    Assert.assertEquals(preparedLayer1, preparedLayersCache.get(digest1).get());\n    Assert.assertEquals(preparedLayer2, preparedLayersCache.get(digest2).get());\n\n    // 2. Should not schedule threads for existing layers.\n    stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory);\n    Assert.assertEquals(2, preparedLayersCache.size()); // no new layers cached (still 2)\n    Assert.assertEquals(preparedLayer1, preparedLayersCache.get(digest1).get());\n    Assert.assertEquals(preparedLayer2, preparedLayersCache.get(digest2).get());\n\n    // 3. Another image with one duplicate layer.\n    when(image.getLayers()).thenReturn(ImmutableList.of(layer3, layer2));\n    stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory);\n    Assert.assertEquals(3, preparedLayersCache.size()); // one new layer cached\n    Assert.assertEquals(preparedLayer1, preparedLayersCache.get(digest1).get());\n    Assert.assertEquals(preparedLayer2, preparedLayersCache.get(digest2).get());\n    Assert.assertEquals(preparedLayer3, preparedLayersCache.get(digest3).get());\n\n    // Total three threads scheduled for the three unique layers.\n    Mockito.verify(executorService, Mockito.times(3))\n        .submit(Mockito.any(ObtainBaseImageLayerStep.class));\n  }\n\n  @Test\n  public void testIsImagePushed_skipExistingEnabledAndManifestPresent() {\n    Optional<ManifestAndDigest<ManifestTemplate>> manifestResult = Mockito.mock(Optional.class);\n    when(manifestResult.isPresent()).thenReturn(true);\n    System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, \"true\");\n\n    Assert.assertFalse(stepsRunner.isImagePushed(manifestResult));\n  }\n\n  @Test\n  public void testIsImagePushed_skipExistingImageDisabledAndManifestPresent() {\n    Optional<ManifestAndDigest<ManifestTemplate>> manifestResult = Mockito.mock(Optional.class);\n    System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, \"false\");\n\n    Assert.assertTrue(stepsRunner.isImagePushed(manifestResult));\n  }\n\n  @Test\n  public void testIsImagePushed_skipExistingImageEnabledAndManifestNotPresent() {\n    Optional<ManifestAndDigest<ManifestTemplate>> manifestResult = Mockito.mock(Optional.class);\n    System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, \"true\");\n    when(manifestResult.isPresent()).thenReturn(false);\n\n    Assert.assertTrue(stepsRunner.isImagePushed(manifestResult));\n  }\n\n  @Test\n  public void testFetchBuildImageForLocalBuild_matchingOsAndArch()\n      throws ExecutionException, InterruptedException {\n    when(builtArm64AndLinuxImage.getArchitecture()).thenReturn(\"arm64\");\n    when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn(\"amd64\");\n    when(builtAmd64AndWindowsImage.getOs()).thenReturn(\"windows\");\n    when(executorService.submit(Mockito.any(Callable.class)))\n        .thenReturn(\n            Futures.immediateFuture(\n                ImmutableMap.of(\n                    baseImage1,\n                    Futures.immediateFuture(builtArm64AndLinuxImage),\n                    baseImage2,\n                    Futures.immediateFuture(builtAmd64AndWindowsImage))));\n    stepsRunner.buildImages(progressDispatcherFactory);\n\n    Image expectedImage =\n        stepsRunner.fetchBuiltImageForLocalBuild(\"windows\", \"amd64\", eventHandlers);\n\n    assertThat(expectedImage.getOs()).isEqualTo(\"windows\");\n    assertThat(expectedImage.getArchitecture()).isEqualTo(\"amd64\");\n  }\n\n  @Test\n  public void testFetchBuildImageForLocalBuild_differentOs_buildImageForFirstPlatform()\n      throws ExecutionException, InterruptedException {\n    when(builtArm64AndLinuxImage.getArchitecture()).thenReturn(\"arm64\");\n    when(builtArm64AndLinuxImage.getOs()).thenReturn(\"linux\");\n    when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn(\"amd64\");\n    when(executorService.submit(Mockito.any(Callable.class)))\n        .thenReturn(\n            Futures.immediateFuture(\n                ImmutableMap.of(\n                    baseImage1,\n                    Futures.immediateFuture(builtArm64AndLinuxImage),\n                    baseImage2,\n                    Futures.immediateFuture(builtAmd64AndWindowsImage))));\n    stepsRunner.buildImages(progressDispatcherFactory);\n\n    Image expectedImage = stepsRunner.fetchBuiltImageForLocalBuild(\"os\", \"arm64\", eventHandlers);\n\n    assertThat(expectedImage.getOs()).isEqualTo(\"linux\");\n    assertThat(expectedImage.getArchitecture()).isEqualTo(\"arm64\");\n  }\n\n  @Test\n  public void testFetchBuildImageForLocalBuild_differentArch_buildImageForFirstPlatform()\n      throws ExecutionException, InterruptedException {\n    when(builtArm64AndLinuxImage.getArchitecture()).thenReturn(\"arm64\");\n    when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn(\"amd64\");\n    when(executorService.submit(Mockito.any(Callable.class)))\n        .thenReturn(\n            Futures.immediateFuture(\n                ImmutableMap.of(\n                    baseImage1,\n                    Futures.immediateFuture(builtArm64AndLinuxImage),\n                    baseImage2,\n                    Futures.immediateFuture(builtAmd64AndWindowsImage))));\n    stepsRunner.buildImages(progressDispatcherFactory);\n\n    Image expectedImage = stepsRunner.fetchBuiltImageForLocalBuild(\"linux\", \"arch\", eventHandlers);\n\n    assertThat(expectedImage.getArchitecture()).isEqualTo(\"arm64\");\n  }\n\n  @Test\n  public void testFetchBuildImageForLocalBuild_singleImage_imagePlatformDifferentFromDockerEnv()\n      throws ExecutionException, InterruptedException {\n    when(builtArm64AndLinuxImage.getArchitecture()).thenReturn(\"arm64\");\n    when(builtArm64AndLinuxImage.getOs()).thenReturn(\"linux\");\n    when(executorService.submit(Mockito.any(Callable.class)))\n        .thenReturn(\n            Futures.immediateFuture(\n                ImmutableMap.of(baseImage1, Futures.immediateFuture(builtArm64AndLinuxImage))));\n    stepsRunner.buildImages(progressDispatcherFactory);\n\n    Image expectedImage = stepsRunner.fetchBuiltImageForLocalBuild(\"linux\", \"amd64\", eventHandlers);\n\n    assertThat(expectedImage.getOs()).isEqualTo(\"linux\");\n    assertThat(expectedImage.getArchitecture()).isEqualTo(\"arm64\");\n  }\n\n  @Test\n  public void testNormalizeArchitecture_aarch64() {\n    assertThat(stepsRunner.normalizeArchitecture(\"aarch64\")).isEqualTo(\"arm64\");\n  }\n\n  @Test\n  public void testNormalizeArchitecture_x86_64() {\n    assertThat(stepsRunner.normalizeArchitecture(\"x86_64\")).isEqualTo(\"amd64\");\n  }\n\n  @Test\n  public void testNormalizeArchitecture_arm() {\n    assertThat(stepsRunner.normalizeArchitecture(\"arm\")).isEqualTo(\"arm\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageFilesTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link CacheStorageFiles}. */\npublic class CacheStorageFilesTest {\n\n  private static final CacheStorageFiles TEST_CACHE_STORAGE_FILES =\n      new CacheStorageFiles(Paths.get(\"cache/directory\"));\n\n  @Test\n  public void testIsLayerFile() {\n    Assert.assertTrue(\n        CacheStorageFiles.isLayerFile(\n            Paths.get(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")));\n    Assert.assertTrue(\n        CacheStorageFiles.isLayerFile(\n            Paths.get(\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\")));\n    Assert.assertFalse(CacheStorageFiles.isLayerFile(Paths.get(\"is.not.layer.file\")));\n  }\n\n  @Test\n  public void testGetDiffId() throws DigestException, CacheCorruptedException {\n    Assert.assertEquals(\n        DescriptorDigest.fromHash(\n            \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"),\n        TEST_CACHE_STORAGE_FILES.getDigestFromFilename(\n            Paths.get(\n                \"layer\",\n                \"file\",\n                \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\")));\n    Assert.assertEquals(\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"),\n        TEST_CACHE_STORAGE_FILES.getDigestFromFilename(\n            Paths.get(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")));\n  }\n\n  @Test\n  public void testGetDiffId_corrupted() {\n    try {\n      TEST_CACHE_STORAGE_FILES.getDigestFromFilename(Paths.get(\"not long enough\"));\n      Assert.fail(\"Should have thrown CacheCorruptedException\");\n\n    } catch (CacheCorruptedException ex) {\n      Assert.assertEquals(\n          \"Layer file did not include valid hash: not long enough. \"\n              + \"You may need to clear the cache by deleting the '\"\n              + TEST_CACHE_STORAGE_FILES.getCacheDirectory()\n              + \"' directory\",\n          ex.getMessage());\n      MatcherAssert.assertThat(ex.getCause(), CoreMatchers.instanceOf(DigestException.class));\n    }\n\n    try {\n      TEST_CACHE_STORAGE_FILES.getDigestFromFilename(\n          Paths.get(\n              \"not valid hash bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"));\n      Assert.fail(\"Should have thrown CacheCorruptedException\");\n    } catch (CacheCorruptedException ex) {\n      Assert.assertEquals(\n          \"Layer file did not include valid hash: \"\n              + \"not valid hash bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb. \"\n              + \"You may need to clear the cache by deleting the '\"\n              + TEST_CACHE_STORAGE_FILES.getCacheDirectory()\n              + \"' directory\",\n          ex.getMessage());\n      MatcherAssert.assertThat(ex.getCause(), CoreMatchers.instanceOf(DigestException.class));\n    }\n  }\n\n  @Test\n  public void testGetLayerFile() throws DigestException {\n    DescriptorDigest layerDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    DescriptorDigest diffId =\n        DescriptorDigest.fromHash(\n            \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\");\n\n    Assert.assertEquals(\n        Paths.get(\n            \"cache\",\n            \"directory\",\n            \"layers\",\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n            \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"),\n        TEST_CACHE_STORAGE_FILES.getLayerFile(layerDigest, diffId));\n  }\n\n  @Test\n  public void testGetLayerFilename() throws DigestException {\n    DescriptorDigest diffId =\n        DescriptorDigest.fromHash(\n            \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\");\n\n    Assert.assertEquals(\n        \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\n        TEST_CACHE_STORAGE_FILES.getLayerFilename(diffId));\n  }\n\n  @Test\n  public void testGetSelectorFile() throws DigestException {\n    DescriptorDigest selector =\n        DescriptorDigest.fromHash(\n            \"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\");\n\n    Assert.assertEquals(\n        Paths.get(\n            \"cache\",\n            \"directory\",\n            \"selectors\",\n            \"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"),\n        TEST_CACHE_STORAGE_FILES.getSelectorFile(selector));\n  }\n\n  @Test\n  public void testGetLayersDirectory() {\n    Assert.assertEquals(\n        Paths.get(\"cache\", \"directory\", \"layers\"), TEST_CACHE_STORAGE_FILES.getLayersDirectory());\n  }\n\n  @Test\n  public void testGetLayerDirectory() throws DigestException {\n    DescriptorDigest layerDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n\n    Assert.assertEquals(\n        Paths.get(\n            \"cache\",\n            \"directory\",\n            \"layers\",\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"),\n        TEST_CACHE_STORAGE_FILES.getLayerDirectory(layerDigest));\n  }\n\n  @Test\n  public void testGetTemporaryDirectory() {\n    Assert.assertEquals(\n        Paths.get(\"cache/directory/tmp\"), TEST_CACHE_STORAGE_FILES.getTemporaryDirectory());\n  }\n\n  @Test\n  public void testGetImagesDirectory() {\n    Assert.assertEquals(\n        Paths.get(\"cache/directory/images\"), TEST_CACHE_STORAGE_FILES.getImagesDirectory());\n  }\n\n  @Test\n  public void testGetImageDirectory() throws InvalidImageReferenceException {\n    Path imagesDirectory = Paths.get(\"cache\", \"directory\", \"images\");\n    Assert.assertEquals(imagesDirectory, TEST_CACHE_STORAGE_FILES.getImagesDirectory());\n\n    Assert.assertEquals(\n        imagesDirectory.resolve(\"reg.istry/repo/sitory!tag\"),\n        TEST_CACHE_STORAGE_FILES.getImageDirectory(\n            ImageReference.parse(\"reg.istry/repo/sitory:tag\")));\n    Assert.assertEquals(\n        imagesDirectory.resolve(\n            \"reg.istry/repo!sha256!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"),\n        TEST_CACHE_STORAGE_FILES.getImageDirectory(\n            ImageReference.parse(\n                \"reg.istry/repo@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\")));\n    Assert.assertEquals(\n        imagesDirectory.resolve(\"reg.istry!5000/repo/sitory!tag\"),\n        TEST_CACHE_STORAGE_FILES.getImageDirectory(\n            ImageReference.parse(\"reg.istry:5000/repo/sitory:tag\")));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageReaderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate.ContentDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.mockito.Mockito;\n\n/** Tests for {@link CacheStorageReader}. */\npublic class CacheStorageReaderTest {\n\n  private static void setupCachedMetadataV21(Path cacheDirectory)\n      throws IOException, URISyntaxException {\n    Path imageDirectory = cacheDirectory.resolve(\"images/test/image!tag\");\n    Files.createDirectories(imageDirectory);\n\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            loadJsonResource(\"core/json/v21manifest.json\", V21ManifestTemplate.class), null);\n    try (OutputStream out =\n        Files.newOutputStream(imageDirectory.resolve(\"manifests_configs.json\"))) {\n      JsonTemplateMapper.writeTo(\n          new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)), out);\n    }\n  }\n\n  private static void setupCachedMetadataV22(Path cacheDirectory)\n      throws IOException, URISyntaxException {\n    Path imageDirectory = cacheDirectory.resolve(\"images/test/image!tag\");\n    Files.createDirectories(imageDirectory);\n\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            loadJsonResource(\"core/json/v22manifest.json\", V22ManifestTemplate.class),\n            loadJsonResource(\n                \"core/json/containerconfig.json\", ContainerConfigurationTemplate.class),\n            \"sha256:digest\");\n    try (OutputStream out =\n        Files.newOutputStream(imageDirectory.resolve(\"manifests_configs.json\"))) {\n      JsonTemplateMapper.writeTo(\n          new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)), out);\n    }\n  }\n\n  private static void setupCachedMetadataV22ManifestList(Path cacheDirectory)\n      throws IOException, URISyntaxException {\n    Path imageDirectory = cacheDirectory.resolve(\"images/test/image!tag\");\n    Files.createDirectories(imageDirectory);\n\n    ManifestTemplate v22ManifestList =\n        loadJsonResource(\"core/json/v22manifest_list.json\", V22ManifestListTemplate.class);\n    ManifestTemplate v22Manifest1 =\n        loadJsonResource(\"core/json/v22manifest.json\", V22ManifestTemplate.class);\n    ManifestTemplate v22Manifest2 =\n        loadJsonResource(\"core/json/translated_v22manifest.json\", V22ManifestTemplate.class);\n    ContainerConfigurationTemplate containerConfig =\n        loadJsonResource(\"core/json/containerconfig.json\", ContainerConfigurationTemplate.class);\n    List<ManifestAndConfigTemplate> manifestsAndConfigs =\n        Arrays.asList(\n            new ManifestAndConfigTemplate(v22Manifest1, containerConfig, \"sha256:digest\"),\n            new ManifestAndConfigTemplate(v22Manifest2, containerConfig, \"sha256:digest\"));\n    try (OutputStream out =\n        Files.newOutputStream(imageDirectory.resolve(\"manifests_configs.json\"))) {\n      JsonTemplateMapper.writeTo(\n          new ImageMetadataTemplate(v22ManifestList, manifestsAndConfigs), out);\n    }\n  }\n\n  private static void setupCachedMetadataOci(Path cacheDirectory)\n      throws IOException, URISyntaxException {\n    Path imageDirectory = cacheDirectory.resolve(\"images/test/image!tag\");\n    Files.createDirectories(imageDirectory);\n\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            loadJsonResource(\"core/json/ocimanifest.json\", OciManifestTemplate.class),\n            loadJsonResource(\n                \"core/json/containerconfig.json\", ContainerConfigurationTemplate.class),\n            \"sha256:digest\");\n    try (OutputStream out =\n        Files.newOutputStream(imageDirectory.resolve(\"manifests_configs.json\"))) {\n      JsonTemplateMapper.writeTo(\n          new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)), out);\n    }\n  }\n\n  private static void setupCachedMetadataOciImageIndex(Path cacheDirectory)\n      throws IOException, URISyntaxException {\n    Path imageDirectory = cacheDirectory.resolve(\"images/test/image!tag\");\n    Files.createDirectories(imageDirectory);\n\n    ManifestTemplate ociIndex = loadJsonResource(\"core/json/ociindex.json\", OciIndexTemplate.class);\n    ManifestTemplate ociManifest =\n        loadJsonResource(\"core/json/ocimanifest.json\", OciManifestTemplate.class);\n    ContainerConfigurationTemplate containerConfig =\n        loadJsonResource(\"core/json/containerconfig.json\", ContainerConfigurationTemplate.class);\n    List<ManifestAndConfigTemplate> manifestsAndConfigs =\n        Arrays.asList(new ManifestAndConfigTemplate(ociManifest, containerConfig, \"sha256:digest\"));\n    try (OutputStream out =\n        Files.newOutputStream(imageDirectory.resolve(\"manifests_configs.json\"))) {\n      JsonTemplateMapper.writeTo(new ImageMetadataTemplate(ociIndex, manifestsAndConfigs), out);\n    }\n  }\n\n  private static <T extends JsonTemplate> T loadJsonResource(String path, Class<T> jsonClass)\n      throws URISyntaxException, IOException {\n    return JsonTemplateMapper.readJsonFromFile(\n        Paths.get(Resources.getResource(path).toURI()), jsonClass);\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private Path cacheDirectory;\n  private DescriptorDigest layerDigest1;\n  private DescriptorDigest layerDigest2;\n  private CacheStorageFiles cacheStorageFiles;\n  private CacheStorageReader cacheStorageReader;\n\n  @Before\n  public void setUp() throws DigestException, IOException {\n    cacheDirectory = temporaryFolder.newFolder().toPath();\n    cacheStorageFiles = new CacheStorageFiles(cacheDirectory);\n    cacheStorageReader = new CacheStorageReader(cacheStorageFiles);\n    layerDigest1 =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    layerDigest2 =\n        DescriptorDigest.fromHash(\n            \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\");\n  }\n\n  @Test\n  public void testRetrieveMetadata_v21SingleManifest()\n      throws IOException, URISyntaxException, CacheCorruptedException {\n    setupCachedMetadataV21(cacheDirectory);\n\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n    Assert.assertNull(metadata.getManifestList());\n    Assert.assertEquals(1, metadata.getManifestsAndConfigs().size());\n    Assert.assertNull(metadata.getManifestsAndConfigs().get(0).getConfig());\n\n    V21ManifestTemplate manifestTemplate =\n        (V21ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest();\n    Assert.assertEquals(1, manifestTemplate.getSchemaVersion());\n    Assert.assertEquals(2, manifestTemplate.getLayerDigests().size());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifestTemplate.getLayerDigests().get(0).getHash());\n    Assert.assertEquals(\n        \"5bd451067f9ab05e97cda8476c82f86d9b69c2dffb60a8ad2fe3723942544ab3\",\n        manifestTemplate.getLayerDigests().get(1).getHash());\n  }\n\n  @Test\n  public void testRetrieveMetadata_v22SingleManifest()\n      throws IOException, URISyntaxException, CacheCorruptedException {\n    setupCachedMetadataV22(cacheDirectory);\n\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n    Assert.assertNull(metadata.getManifestList());\n    Assert.assertEquals(1, metadata.getManifestsAndConfigs().size());\n\n    V22ManifestTemplate manifestTemplate =\n        (V22ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest();\n    Assert.assertEquals(2, manifestTemplate.getSchemaVersion());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifestTemplate.getContainerConfiguration().getDigest().getHash());\n  }\n\n  @Test\n  public void testRetrieveMetadata_v22ManifestList()\n      throws IOException, URISyntaxException, CacheCorruptedException {\n    setupCachedMetadataV22ManifestList(cacheDirectory);\n\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n\n    MatcherAssert.assertThat(\n        metadata.getManifestList(), CoreMatchers.instanceOf(V22ManifestListTemplate.class));\n    List<ManifestDescriptorTemplate> manifestDescriptors =\n        ((V22ManifestListTemplate) metadata.getManifestList()).getManifests();\n\n    Assert.assertEquals(3, manifestDescriptors.size());\n    Assert.assertEquals(\n        \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n        manifestDescriptors.get(0).getDigest());\n    Assert.assertEquals(\n        \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n        manifestDescriptors.get(1).getDigest());\n    Assert.assertEquals(\n        \"sha256:cccbcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501999\",\n        manifestDescriptors.get(2).getDigest());\n\n    Assert.assertEquals(2, metadata.getManifestsAndConfigs().size());\n    ManifestAndConfigTemplate manifestAndConfig1 = metadata.getManifestsAndConfigs().get(0);\n    ManifestAndConfigTemplate manifestAndConfig2 = metadata.getManifestsAndConfigs().get(1);\n\n    V22ManifestTemplate manifest1 = (V22ManifestTemplate) manifestAndConfig1.getManifest();\n    V22ManifestTemplate manifest2 = (V22ManifestTemplate) manifestAndConfig2.getManifest();\n    Assert.assertEquals(2, manifest1.getSchemaVersion());\n    Assert.assertEquals(2, manifest2.getSchemaVersion());\n    Assert.assertEquals(1, manifest1.getLayers().size());\n    Assert.assertEquals(1, manifest2.getLayers().size());\n    Assert.assertEquals(\n        \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\",\n        manifest1.getLayers().get(0).getDigest().getHash());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifest2.getLayers().get(0).getDigest().getHash());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifest1.getContainerConfiguration().getDigest().getHash());\n    Assert.assertEquals(\n        \"2000a70a1ce8bba401c493376fdb9eb81bcf7155212f4ce4c6ff96e577718a49\",\n        manifest2.getContainerConfiguration().getDigest().getHash());\n\n    Assert.assertEquals(\"wasm\", manifestAndConfig1.getConfig().getArchitecture());\n    Assert.assertEquals(\"wasm\", manifestAndConfig2.getConfig().getArchitecture());\n  }\n\n  @Test\n  public void testRetrieveMetadata_ociSingleManifest()\n      throws IOException, URISyntaxException, CacheCorruptedException {\n    setupCachedMetadataOci(cacheDirectory);\n\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n    Assert.assertNull(metadata.getManifestList());\n    Assert.assertEquals(1, metadata.getManifestsAndConfigs().size());\n\n    OciManifestTemplate manifestTemplate =\n        (OciManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest();\n    Assert.assertEquals(2, manifestTemplate.getSchemaVersion());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifestTemplate.getContainerConfiguration().getDigest().getHash());\n  }\n\n  @Test\n  public void testRetrieveMetadata_ociImageIndex()\n      throws IOException, URISyntaxException, CacheCorruptedException {\n    setupCachedMetadataOciImageIndex(cacheDirectory);\n\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n\n    MatcherAssert.assertThat(\n        metadata.getManifestList(), CoreMatchers.instanceOf(OciIndexTemplate.class));\n    List<? extends ContentDescriptorTemplate> manifestDescriptors =\n        ((OciIndexTemplate) metadata.getManifestList()).getManifests();\n\n    Assert.assertEquals(1, manifestDescriptors.size());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifestDescriptors.get(0).getDigest().getHash());\n\n    Assert.assertEquals(1, metadata.getManifestsAndConfigs().size());\n    ManifestAndConfigTemplate manifestAndConfig = metadata.getManifestsAndConfigs().get(0);\n\n    OciManifestTemplate manifestTemplate = (OciManifestTemplate) manifestAndConfig.getManifest();\n    Assert.assertEquals(2, manifestTemplate.getSchemaVersion());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        manifestTemplate.getContainerConfiguration().getDigest().getHash());\n\n    Assert.assertEquals(\"wasm\", manifestAndConfig.getConfig().getArchitecture());\n  }\n\n  @Test\n  public void testRetrieveMetadata_containerConfiguration()\n      throws IOException, URISyntaxException, CacheCorruptedException {\n    setupCachedMetadataV22(cacheDirectory);\n\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n    Assert.assertNull(metadata.getManifestList());\n    Assert.assertEquals(1, metadata.getManifestsAndConfigs().size());\n\n    ContainerConfigurationTemplate configurationTemplate =\n        metadata.getManifestsAndConfigs().get(0).getConfig();\n    Assert.assertNotNull(configurationTemplate);\n    Assert.assertEquals(\"wasm\", configurationTemplate.getArchitecture());\n    Assert.assertEquals(\"js\", configurationTemplate.getOs());\n  }\n\n  @Test\n  public void testRetrieve() throws IOException, CacheCorruptedException {\n    // Creates the test layer directory.\n    DescriptorDigest layerDigest = layerDigest1;\n    DescriptorDigest layerDiffId = layerDigest2;\n    Files.createDirectories(cacheStorageFiles.getLayerDirectory(layerDigest));\n    try (OutputStream out =\n        Files.newOutputStream(cacheStorageFiles.getLayerFile(layerDigest, layerDiffId))) {\n      out.write(\"layerBlob\".getBytes(StandardCharsets.UTF_8));\n    }\n\n    // Checks that the CachedLayer is retrieved correctly.\n    Optional<CachedLayer> optionalCachedLayer = cacheStorageReader.retrieve(layerDigest);\n    Assert.assertTrue(optionalCachedLayer.isPresent());\n    Assert.assertEquals(layerDigest, optionalCachedLayer.get().getDigest());\n    Assert.assertEquals(layerDiffId, optionalCachedLayer.get().getDiffId());\n    Assert.assertEquals(\"layerBlob\".length(), optionalCachedLayer.get().getSize());\n    Assert.assertEquals(\"layerBlob\", Blobs.writeToString(optionalCachedLayer.get().getBlob()));\n\n    // Checks that multiple .layer files means the cache is corrupted.\n    Files.createFile(cacheStorageFiles.getLayerFile(layerDigest, layerDigest));\n    try {\n      cacheStorageReader.retrieve(layerDigest);\n      Assert.fail(\"Should have thrown CacheCorruptedException\");\n\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\n              \"No or multiple layer files found for layer hash \"\n                  + layerDigest.getHash()\n                  + \" in directory: \"\n                  + cacheStorageFiles.getLayerDirectory(layerDigest)));\n    }\n  }\n\n  @Test\n  public void testRetrieveTarLayer() throws IOException, CacheCorruptedException {\n    // Creates the test layer directory.\n    Path localDirectory = cacheStorageFiles.getLocalDirectory();\n    DescriptorDigest layerDigest = layerDigest1;\n    DescriptorDigest layerDiffId = layerDigest2;\n    Files.createDirectories(localDirectory.resolve(layerDiffId.getHash()));\n    try (OutputStream out =\n        Files.newOutputStream(\n            localDirectory.resolve(layerDiffId.getHash()).resolve(layerDigest.getHash()))) {\n      out.write(\"layerBlob\".getBytes(StandardCharsets.UTF_8));\n    }\n\n    // Checks that the CachedLayer is retrieved correctly.\n    Optional<CachedLayer> optionalCachedLayer = cacheStorageReader.retrieveTarLayer(layerDiffId);\n    Assert.assertTrue(optionalCachedLayer.isPresent());\n    Assert.assertEquals(layerDigest, optionalCachedLayer.get().getDigest());\n    Assert.assertEquals(layerDiffId, optionalCachedLayer.get().getDiffId());\n    Assert.assertEquals(\"layerBlob\".length(), optionalCachedLayer.get().getSize());\n    Assert.assertEquals(\"layerBlob\", Blobs.writeToString(optionalCachedLayer.get().getBlob()));\n\n    // Checks that multiple layer files means the cache is corrupted.\n    Files.createFile(localDirectory.resolve(layerDiffId.getHash()).resolve(layerDiffId.getHash()));\n    try {\n      cacheStorageReader.retrieveTarLayer(layerDiffId);\n      Assert.fail(\"Should have thrown CacheCorruptedException\");\n\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\n              \"No or multiple layer files found for layer hash \"\n                  + layerDiffId.getHash()\n                  + \" in directory: \"\n                  + localDirectory.resolve(layerDiffId.getHash())));\n    }\n  }\n\n  @Test\n  public void testRetrieveLocalConfig() throws IOException, URISyntaxException, DigestException {\n    Path configDirectory = cacheDirectory.resolve(\"local\").resolve(\"config\");\n    Files.createDirectories(configDirectory);\n    Files.copy(\n        Paths.get(Resources.getResource(\"core/json/containerconfig.json\").toURI()),\n        configDirectory.resolve(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"));\n\n    ContainerConfigurationTemplate configurationTemplate =\n        cacheStorageReader\n            .retrieveLocalConfig(\n                DescriptorDigest.fromHash(\n                    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"))\n            .get();\n    Assert.assertEquals(\"wasm\", configurationTemplate.getArchitecture());\n    Assert.assertEquals(\"js\", configurationTemplate.getOs());\n\n    Optional<ContainerConfigurationTemplate> missingConfigurationTemplate =\n        cacheStorageReader.retrieveLocalConfig(\n            DescriptorDigest.fromHash(\n                \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"));\n    Assert.assertFalse(missingConfigurationTemplate.isPresent());\n  }\n\n  @Test\n  public void testSelect_invalidLayerDigest() throws IOException {\n    DescriptorDigest selector = layerDigest1;\n    Path selectorFile = cacheStorageFiles.getSelectorFile(selector);\n    Files.createDirectories(selectorFile.getParent());\n    Files.write(selectorFile, \"not a valid layer digest\".getBytes(StandardCharsets.UTF_8));\n\n    try {\n      cacheStorageReader.select(selector);\n      Assert.fail(\"Should have thrown CacheCorruptedException\");\n\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\n              \"Expected valid layer digest as contents of selector file `\"\n                  + selectorFile\n                  + \"` for selector `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`, but got: not a valid layer digest\"));\n    }\n  }\n\n  @Test\n  public void testSelect() throws IOException, CacheCorruptedException {\n    DescriptorDigest selector = layerDigest1;\n    Path selectorFile = cacheStorageFiles.getSelectorFile(selector);\n    Files.createDirectories(selectorFile.getParent());\n    Files.write(selectorFile, layerDigest2.getHash().getBytes(StandardCharsets.UTF_8));\n\n    Optional<DescriptorDigest> selectedLayerDigest = cacheStorageReader.select(selector);\n    Assert.assertTrue(selectedLayerDigest.isPresent());\n    Assert.assertEquals(layerDigest2, selectedLayerDigest.get());\n  }\n\n  @Test\n  public void testVerifyImageMetadata_manifestCacheEmpty() {\n    ImageMetadataTemplate metadata = new ImageMetadataTemplate(null, Collections.emptyList());\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.startsWith(\"Manifest cache empty\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_manifestListMissing() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V22ManifestListTemplate(), new ContainerConfigurationTemplate());\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig, manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.startsWith(\"Manifest list missing\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_manifestsMissing() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(null, new ContainerConfigurationTemplate());\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.startsWith(\"Manifest(s) missing\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_schema1ManifestsCorrupted_manifestListExists() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(new V21ManifestTemplate(), null);\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(new V22ManifestListTemplate(), Arrays.asList(manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.startsWith(\"Schema 1 manifests corrupted\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_schema1ManifestsCorrupted_containerConfigExists() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V21ManifestTemplate(), new ContainerConfigurationTemplate());\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.startsWith(\"Schema 1 manifests corrupted\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_schema2ManifestsCorrupted_nullContainerConfig() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(new V22ManifestTemplate(), null, \"sha256:digest\");\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.startsWith(\"Schema 2 manifests corrupted\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_schema2ManifestsCorrupted_nullManifestDigest() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V22ManifestTemplate(), new ContainerConfigurationTemplate(), null);\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(new V22ManifestListTemplate(), Arrays.asList(manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.startsWith(\"Schema 2 manifests corrupted\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_unknownManifestType() {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            Mockito.mock(ManifestTemplate.class), new ContainerConfigurationTemplate());\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    try {\n      CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n      Assert.fail();\n    } catch (CacheCorruptedException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.startsWith(\"Unknown manifest type:\"));\n    }\n  }\n\n  @Test\n  public void testVerifyImageMetadata_validV21() throws CacheCorruptedException {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(new V21ManifestTemplate(), null);\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n    // should pass without CacheCorruptedException\n  }\n\n  @Test\n  public void testVerifyImageMetadata_validV22() throws CacheCorruptedException {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V22ManifestTemplate(), new ContainerConfigurationTemplate());\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n    // should pass without CacheCorruptedException\n  }\n\n  @Test\n  public void testVerifyImageMetadata_validV22ManifestList() throws CacheCorruptedException {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new V22ManifestTemplate(), new ContainerConfigurationTemplate(), \"sha256:digest\");\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(\n            new V22ManifestListTemplate(), Arrays.asList(manifestAndConfig, manifestAndConfig));\n    CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n    // should pass without CacheCorruptedException\n  }\n\n  @Test\n  public void testVerifyImageMetadata_validOci() throws CacheCorruptedException {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new OciManifestTemplate(), new ContainerConfigurationTemplate(), \"sha256:digest\");\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig));\n    CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n    // should pass without CacheCorruptedException\n  }\n\n  @Test\n  public void testVerifyImageMetadata_validOciImageIndex() throws CacheCorruptedException {\n    ManifestAndConfigTemplate manifestAndConfig =\n        new ManifestAndConfigTemplate(\n            new OciManifestTemplate(), new ContainerConfigurationTemplate(), \"sha256:digest\");\n    ImageMetadataTemplate metadata =\n        new ImageMetadataTemplate(\n            new OciIndexTemplate(), Arrays.asList(manifestAndConfig, manifestAndConfig));\n    CacheStorageReader.verifyImageMetadata(metadata, Paths.get(\"/cache/dir\"));\n    // should pass without CacheCorruptedException\n  }\n\n  @Test\n  public void testAllLayersCached_v21SingleManifest()\n      throws IOException, CacheCorruptedException, DigestException, URISyntaxException {\n    setupCachedMetadataV21(cacheDirectory);\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n    V21ManifestTemplate manifest =\n        (V21ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest();\n    DescriptorDigest firstLayerDigest = manifest.getLayerDigests().get(0);\n    DescriptorDigest secondLayerDigest = manifest.getLayerDigests().get(1);\n\n    // Create only one of the layer directories so that layers are only partially cached.\n    Files.createDirectories(cacheStorageFiles.getLayerDirectory(firstLayerDigest));\n    boolean checkWithPartialLayersCached = cacheStorageReader.areAllLayersCached(manifest);\n    // Create the other layer directory so that all layers are cached.\n    Files.createDirectories(cacheStorageFiles.getLayerDirectory(secondLayerDigest));\n    boolean checkWithAllLayersCached = cacheStorageReader.areAllLayersCached(manifest);\n\n    assertThat(checkWithPartialLayersCached).isFalse();\n    assertThat(checkWithAllLayersCached).isTrue();\n  }\n\n  @Test\n  public void testAllLayersCached_v22SingleManifest()\n      throws IOException, CacheCorruptedException, DigestException, URISyntaxException {\n    setupCachedMetadataV22(cacheDirectory);\n    ImageMetadataTemplate metadata =\n        cacheStorageReader.retrieveMetadata(ImageReference.of(\"test\", \"image\", \"tag\")).get();\n    V22ManifestTemplate manifest =\n        (V22ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest();\n    DescriptorDigest layerDigest = manifest.getLayers().get(0).getDigest();\n\n    boolean checkBeforeLayerCached = cacheStorageReader.areAllLayersCached(manifest);\n    // Create the single layer directory so that all layers are cached.\n    Files.createDirectories(cacheStorageFiles.getLayerDirectory(layerDigest));\n    boolean checkAfterLayerCached = cacheStorageReader.areAllLayersCached(manifest);\n\n    assertThat(checkBeforeLayerCached).isFalse();\n    assertThat(checkAfterLayerCached).isTrue();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageWriterTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate.ContentDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.ImageMetadataTemplate;\nimport com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.NoSuchFileException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.zip.GZIPOutputStream;\nimport org.apache.commons.compress.compressors.CompressorException;\nimport org.apache.commons.compress.compressors.CompressorStreamFactory;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link CacheStorageWriter}. */\npublic class CacheStorageWriterTest {\n\n  private static BlobDescriptor getDigest(Blob blob) throws IOException {\n    return blob.writeTo(ByteStreams.nullOutputStream());\n  }\n\n  private static Blob compress(Blob blob) {\n    // Don't use GzipCompressorOutputStream, which has different defaults than GZIPOutputStream\n    return Blobs.from(\n        outputStream -> {\n          try (GZIPOutputStream compressorStream = new GZIPOutputStream(outputStream)) {\n            blob.writeTo(compressorStream);\n          }\n        },\n        false);\n  }\n\n  private static Blob compress(Blob blob, String compressorName) {\n    return Blobs.from(\n        outputStream -> {\n          try (OutputStream compressorStream =\n              CompressorStreamFactory.getSingleton()\n                  .createCompressorOutputStream(compressorName, outputStream)) {\n            blob.writeTo(compressorStream);\n          } catch (CompressorException e) {\n            throw new RuntimeException(e);\n          }\n        },\n        false);\n  }\n\n  private static Blob decompress(Blob blob) throws IOException {\n    try {\n      return Blobs.from(\n          CompressorStreamFactory.getSingleton()\n              .createCompressorInputStream(new ByteArrayInputStream(Blobs.writeToByteArray(blob))));\n    } catch (CompressorException e) {\n      throw new IOException(e);\n    }\n  }\n\n  private static <T extends JsonTemplate> T loadJsonResource(String path, Class<T> jsonClass)\n      throws URISyntaxException, IOException {\n    return JsonTemplateMapper.readJsonFromFile(\n        Paths.get(Resources.getResource(path).toURI()), jsonClass);\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private CacheStorageFiles cacheStorageFiles;\n  private CacheStorageWriter cacheStorageWriter;\n  private Path cacheRoot;\n\n  @Before\n  public void setUp() throws IOException {\n    cacheRoot = temporaryFolder.newFolder().toPath();\n    cacheStorageFiles = new CacheStorageFiles(cacheRoot);\n    cacheStorageWriter = new CacheStorageWriter(cacheStorageFiles);\n  }\n\n  @Test\n  public void testWriteCompressed() throws IOException {\n    Blob uncompressedLayerBlob = Blobs.from(\"uncompressedLayerBlob\");\n    Blob compressedLayerBlob = compress(uncompressedLayerBlob);\n    CachedLayer cachedLayer = cacheStorageWriter.writeCompressed(compressedLayerBlob);\n\n    verifyCachedLayer(cachedLayer, uncompressedLayerBlob, compressedLayerBlob);\n  }\n\n  @Test\n  public void testWriteZstdCompressed() throws IOException {\n    Blob uncompressedLayerBlob = Blobs.from(\"uncompressedLayerBlob\");\n    Blob compressedLayerBlob = compress(uncompressedLayerBlob, CompressorStreamFactory.ZSTANDARD);\n\n    CachedLayer cachedLayer = cacheStorageWriter.writeCompressed(compressedLayerBlob);\n\n    verifyCachedLayer(cachedLayer, uncompressedLayerBlob, compressedLayerBlob);\n  }\n\n  @Test(expected = IOException.class)\n  public void testWriteCompressWhenUncompressed() throws IOException {\n    Blob uncompressedLayerBlob = Blobs.from(\"uncompressedLayerBlob\");\n    // The detection of compression algorithm will fail\n    cacheStorageWriter.writeCompressed(uncompressedLayerBlob);\n  }\n\n  @Test\n  public void testWriteUncompressed() throws IOException {\n    Blob uncompressedLayerBlob = Blobs.from(\"uncompressedLayerBlob\");\n    DescriptorDigest layerDigest = getDigest(compress(uncompressedLayerBlob)).getDigest();\n    DescriptorDigest selector = getDigest(Blobs.from(\"selector\")).getDigest();\n\n    CachedLayer cachedLayer = cacheStorageWriter.writeUncompressed(uncompressedLayerBlob, selector);\n\n    verifyCachedLayer(cachedLayer, uncompressedLayerBlob, compress(uncompressedLayerBlob));\n\n    // Verifies that the files are present.\n    Path selectorFile = cacheStorageFiles.getSelectorFile(selector);\n    Assert.assertTrue(Files.exists(selectorFile));\n    Assert.assertEquals(layerDigest.getHash(), Blobs.writeToString(Blobs.from(selectorFile)));\n  }\n\n  @Test\n  public void testWriteTarLayer() throws IOException {\n    Blob uncompressedLayerBlob = Blobs.from(\"uncompressedLayerBlob\");\n    DescriptorDigest diffId = getDigest(uncompressedLayerBlob).getDigest();\n\n    CachedLayer cachedLayer =\n        cacheStorageWriter.writeTarLayer(diffId, compress(uncompressedLayerBlob));\n\n    BlobDescriptor layerBlobDescriptor = getDigest(compress(uncompressedLayerBlob));\n\n    // Verifies cachedLayer is correct.\n    Assert.assertEquals(layerBlobDescriptor.getDigest(), cachedLayer.getDigest());\n    Assert.assertEquals(diffId, cachedLayer.getDiffId());\n    Assert.assertEquals(layerBlobDescriptor.getSize(), cachedLayer.getSize());\n    Assert.assertArrayEquals(\n        Blobs.writeToByteArray(uncompressedLayerBlob),\n        Blobs.writeToByteArray(decompress(cachedLayer.getBlob())));\n\n    // Verifies that the files are present.\n    Assert.assertTrue(\n        Files.exists(\n            cacheStorageFiles\n                .getLocalDirectory()\n                .resolve(cachedLayer.getDiffId().getHash())\n                .resolve(cachedLayer.getDigest().getHash())));\n  }\n\n  @Test\n  public void testWriteMetadata_v21()\n      throws IOException, URISyntaxException, InvalidImageReferenceException {\n    V21ManifestTemplate v21Manifest =\n        loadJsonResource(\"core/json/v21manifest.json\", V21ManifestTemplate.class);\n    ImageReference imageReference = ImageReference.parse(\"image.reference/project/thing:tag\");\n\n    ManifestAndConfigTemplate manifestAndConfig = new ManifestAndConfigTemplate(v21Manifest, null);\n    cacheStorageWriter.writeMetadata(\n        imageReference, new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)));\n\n    Path savedMetadataPath =\n        cacheRoot.resolve(\"images/image.reference/project/thing!tag/manifests_configs.json\");\n    Assert.assertTrue(Files.exists(savedMetadataPath));\n\n    ImageMetadataTemplate savedMetadata =\n        JsonTemplateMapper.readJsonFromFile(savedMetadataPath, ImageMetadataTemplate.class);\n    Assert.assertNull(savedMetadata.getManifestList());\n    Assert.assertEquals(1, savedMetadata.getManifestsAndConfigs().size());\n\n    ManifestAndConfigTemplate savedManifestAndConfig =\n        savedMetadata.getManifestsAndConfigs().get(0);\n    Assert.assertNull(savedManifestAndConfig.getConfig());\n\n    V21ManifestTemplate savedManifest = (V21ManifestTemplate) savedManifestAndConfig.getManifest();\n    Assert.assertEquals(\n        \"ppc64le\", savedManifest.getContainerConfiguration().get().getArchitecture());\n  }\n\n  @Test\n  public void testWriteMetadata_v22()\n      throws IOException, URISyntaxException, InvalidImageReferenceException {\n    ContainerConfigurationTemplate containerConfig =\n        loadJsonResource(\"core/json/containerconfig.json\", ContainerConfigurationTemplate.class);\n    V22ManifestTemplate manifest1 =\n        loadJsonResource(\"core/json/v22manifest.json\", V22ManifestTemplate.class);\n    V22ManifestTemplate manifest2 =\n        loadJsonResource(\n            \"core/json/v22manifest_optional_properties.json\", V22ManifestTemplate.class);\n    V22ManifestListTemplate manifestList =\n        loadJsonResource(\"core/json/v22manifest_list.json\", V22ManifestListTemplate.class);\n\n    ImageReference imageReference = ImageReference.parse(\"image.reference/project/thing:tag\");\n\n    List<ManifestAndConfigTemplate> manifestsAndConfigs =\n        Arrays.asList(\n            new ManifestAndConfigTemplate(manifest1, containerConfig, \"sha256:digest\"),\n            new ManifestAndConfigTemplate(manifest2, containerConfig, \"sha256:digest\"));\n    cacheStorageWriter.writeMetadata(\n        imageReference, new ImageMetadataTemplate(manifestList, manifestsAndConfigs));\n\n    Path savedMetadataPath =\n        cacheRoot.resolve(\"images/image.reference/project/thing!tag/manifests_configs.json\");\n    Assert.assertTrue(Files.exists(savedMetadataPath));\n\n    ImageMetadataTemplate savedMetadata =\n        JsonTemplateMapper.readJsonFromFile(savedMetadataPath, ImageMetadataTemplate.class);\n\n    MatcherAssert.assertThat(\n        savedMetadata.getManifestList(), CoreMatchers.instanceOf(V22ManifestListTemplate.class));\n    List<ManifestDescriptorTemplate> savedManifestDescriptors =\n        ((V22ManifestListTemplate) savedMetadata.getManifestList()).getManifests();\n\n    Assert.assertEquals(3, savedManifestDescriptors.size());\n    Assert.assertEquals(\n        \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n        savedManifestDescriptors.get(0).getDigest());\n    Assert.assertEquals(\n        \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n        savedManifestDescriptors.get(1).getDigest());\n    Assert.assertEquals(\n        \"sha256:cccbcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501999\",\n        savedManifestDescriptors.get(2).getDigest());\n\n    Assert.assertEquals(2, savedMetadata.getManifestsAndConfigs().size());\n    ManifestAndConfigTemplate savedManifestAndConfig1 =\n        savedMetadata.getManifestsAndConfigs().get(0);\n    ManifestAndConfigTemplate savedManifestAndConfig2 =\n        savedMetadata.getManifestsAndConfigs().get(1);\n\n    V22ManifestTemplate savedManifest1 =\n        (V22ManifestTemplate) savedManifestAndConfig1.getManifest();\n    V22ManifestTemplate savedManifest2 =\n        (V22ManifestTemplate) savedManifestAndConfig2.getManifest();\n    Assert.assertEquals(2, savedManifest1.getSchemaVersion());\n    Assert.assertEquals(2, savedManifest2.getSchemaVersion());\n\n    Assert.assertEquals(1, savedManifest1.getLayers().size());\n    Assert.assertEquals(\n        \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\",\n        savedManifest1.getLayers().get(0).getDigest().getHash());\n    Assert.assertEquals(\n        Arrays.asList(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"),\n        savedManifest2.getLayers().stream()\n            .map(layer -> layer.getDigest().getHash())\n            .collect(Collectors.toList()));\n\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        savedManifest1.getContainerConfiguration().getDigest().getHash());\n    Assert.assertEquals(\n        \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n        savedManifest2.getContainerConfiguration().getDigest().getHash());\n\n    Assert.assertEquals(\"wasm\", savedManifestAndConfig1.getConfig().getArchitecture());\n    Assert.assertEquals(\"wasm\", savedManifestAndConfig2.getConfig().getArchitecture());\n  }\n\n  @Test\n  public void testWriteMetadata_oci()\n      throws URISyntaxException, IOException, InvalidImageReferenceException {\n    ContainerConfigurationTemplate containerConfig =\n        loadJsonResource(\"core/json/containerconfig.json\", ContainerConfigurationTemplate.class);\n    OciManifestTemplate manifest =\n        loadJsonResource(\"core/json/ocimanifest.json\", OciManifestTemplate.class);\n    OciIndexTemplate ociIndex = loadJsonResource(\"core/json/ociindex.json\", OciIndexTemplate.class);\n\n    ImageReference imageReference = ImageReference.parse(\"image.reference/project/thing:tag\");\n\n    cacheStorageWriter.writeMetadata(\n        imageReference,\n        new ImageMetadataTemplate(\n            ociIndex,\n            Arrays.asList(\n                new ManifestAndConfigTemplate(manifest, containerConfig, \"sha256:digest\"))));\n\n    Path savedMetadataPath =\n        cacheRoot.resolve(\"images/image.reference/project/thing!tag/manifests_configs.json\");\n    Assert.assertTrue(Files.exists(savedMetadataPath));\n\n    ImageMetadataTemplate savedMetadata =\n        JsonTemplateMapper.readJsonFromFile(savedMetadataPath, ImageMetadataTemplate.class);\n\n    MatcherAssert.assertThat(\n        savedMetadata.getManifestList(), CoreMatchers.instanceOf(OciIndexTemplate.class));\n    List<? extends ContentDescriptorTemplate> savedManifestDescriptors =\n        ((OciIndexTemplate) savedMetadata.getManifestList()).getManifests();\n\n    Assert.assertEquals(1, savedManifestDescriptors.size());\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        savedManifestDescriptors.get(0).getDigest().getHash());\n\n    Assert.assertEquals(1, savedMetadata.getManifestsAndConfigs().size());\n    ManifestAndConfigTemplate savedManifestAndConfig =\n        savedMetadata.getManifestsAndConfigs().get(0);\n\n    OciManifestTemplate savedManifest1 = (OciManifestTemplate) savedManifestAndConfig.getManifest();\n    Assert.assertEquals(2, savedManifest1.getSchemaVersion());\n\n    Assert.assertEquals(1, savedManifest1.getLayers().size());\n    Assert.assertEquals(\n        \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\",\n        savedManifest1.getLayers().get(0).getDigest().getHash());\n\n    Assert.assertEquals(\n        \"8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n        savedManifest1.getContainerConfiguration().getDigest().getHash());\n\n    Assert.assertEquals(\"wasm\", savedManifestAndConfig.getConfig().getArchitecture());\n  }\n\n  @Test\n  public void testWriteLocalConfig() throws IOException, URISyntaxException, DigestException {\n    ContainerConfigurationTemplate containerConfigurationTemplate =\n        loadJsonResource(\"core/json/containerconfig.json\", ContainerConfigurationTemplate.class);\n\n    cacheStorageWriter.writeLocalConfig(\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"),\n        containerConfigurationTemplate);\n\n    Path savedConfigPath =\n        cacheStorageFiles\n            .getLocalDirectory()\n            .resolve(\"config\")\n            .resolve(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    Assert.assertTrue(Files.exists(savedConfigPath));\n    ContainerConfigurationTemplate savedContainerConfig =\n        JsonTemplateMapper.readJsonFromFile(savedConfigPath, ContainerConfigurationTemplate.class);\n    Assert.assertEquals(\"wasm\", savedContainerConfig.getArchitecture());\n  }\n\n  @Test\n  public void testMoveIfDoesNotExist_exceptionAfterFailure() {\n    Exception exception =\n        assertThrows(\n            IOException.class,\n            () -> CacheStorageWriter.moveIfDoesNotExist(Paths.get(\"foo\"), Paths.get(\"bar\")));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\n            \"unable to move: foo to bar; such failures are often caused by interference from \"\n                + \"antivirus\");\n    assertThat(exception).hasCauseThat().isInstanceOf(NoSuchFileException.class);\n    assertThat(exception.getCause()).hasMessageThat().isEqualTo(\"foo\");\n  }\n\n  private void verifyCachedLayer(\n      CachedLayer cachedLayer, Blob uncompressedLayerBlob, Blob compressedLayerBlob)\n      throws IOException {\n    BlobDescriptor layerBlobDescriptor = getDigest(compressedLayerBlob);\n    DescriptorDigest layerDiffId = getDigest(uncompressedLayerBlob).getDigest();\n\n    // Verifies cachedLayer is correct.\n    Assert.assertEquals(layerBlobDescriptor.getDigest(), cachedLayer.getDigest());\n    Assert.assertEquals(layerDiffId, cachedLayer.getDiffId());\n    Assert.assertEquals(layerBlobDescriptor.getSize(), cachedLayer.getSize());\n    Assert.assertArrayEquals(\n        Blobs.writeToByteArray(uncompressedLayerBlob),\n        Blobs.writeToByteArray(decompress(cachedLayer.getBlob())));\n\n    // Verifies that the files are present.\n    Assert.assertTrue(\n        Files.exists(\n            cacheStorageFiles.getLayerFile(cachedLayer.getDigest(), cachedLayer.getDiffId())));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.ByteStreams;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.file.FileAlreadyExistsException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link Cache}. */\npublic class CacheTest {\n\n  /**\n   * Gets a {@link Blob} that is {@code blob} compressed. Note that the output stream is closed when\n   * the blob is written.\n   *\n   * @param blob the {@link Blob} to compress\n   * @return the compressed {@link Blob}\n   */\n  private static Blob compress(Blob blob) {\n    return Blobs.from(\n        outputStream -> {\n          try (GZIPOutputStream compressorStream = new GZIPOutputStream(outputStream)) {\n            blob.writeTo(compressorStream);\n          }\n        },\n        false);\n  }\n\n  /**\n   * Gets a {@link Blob} that is {@code blob} decompressed.\n   *\n   * @param blob the {@link Blob} to decompress\n   * @return the decompressed {@link Blob}\n   * @throws IOException if an I/O exception occurs\n   */\n  private static Blob decompress(Blob blob) throws IOException {\n    return Blobs.from(new GZIPInputStream(new ByteArrayInputStream(Blobs.writeToByteArray(blob))));\n  }\n\n  /**\n   * Gets the digest of {@code blob}.\n   *\n   * @param blob the {@link Blob}\n   * @return the {@link DescriptorDigest} of {@code blob}\n   * @throws IOException if an I/O exception occurs\n   */\n  private static DescriptorDigest digestOf(Blob blob) throws IOException {\n    return blob.writeTo(ByteStreams.nullOutputStream()).getDigest();\n  }\n\n  /**\n   * Gets the size of {@code blob}.\n   *\n   * @param blob the {@link Blob}\n   * @return the size (in bytes) of {@code blob}\n   * @throws IOException if an I/O exception occurs\n   */\n  private static long sizeOf(Blob blob) throws IOException {\n    return blob.writeTo(ByteStreams.nullOutputStream()).getSize();\n  }\n\n  private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) {\n    return new FileEntry(\n        source,\n        destination,\n        FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination),\n        FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private Blob layerBlob1;\n  private DescriptorDigest layerDigest1;\n  private DescriptorDigest layerDiffId1;\n  private long layerSize1;\n  private ImmutableList<FileEntry> layerEntries1;\n\n  private Blob layerBlob2;\n  private DescriptorDigest layerDigest2;\n  private DescriptorDigest layerDiffId2;\n  private long layerSize2;\n  private ImmutableList<FileEntry> layerEntries2;\n\n  @Before\n  public void setUp() throws IOException {\n    Path directory = temporaryFolder.newFolder().toPath();\n    Files.createDirectory(directory.resolve(\"source\"));\n    Files.createFile(directory.resolve(\"source/file\"));\n    Files.createDirectories(directory.resolve(\"another/source\"));\n    Files.createFile(directory.resolve(\"another/source/file\"));\n\n    layerBlob1 = Blobs.from(\"layerBlob1\");\n    layerDigest1 = digestOf(compress(layerBlob1));\n    layerDiffId1 = digestOf(layerBlob1);\n    layerSize1 = sizeOf(compress(layerBlob1));\n    layerEntries1 =\n        ImmutableList.of(\n            defaultLayerEntry(\n                directory.resolve(\"source/file\"), AbsoluteUnixPath.get(\"/extraction/path\")),\n            defaultLayerEntry(\n                directory.resolve(\"another/source/file\"),\n                AbsoluteUnixPath.get(\"/another/extraction/path\")));\n\n    layerBlob2 = Blobs.from(\"layerBlob2\");\n    layerDigest2 = digestOf(compress(layerBlob2));\n    layerDiffId2 = digestOf(layerBlob2);\n    layerSize2 = sizeOf(compress(layerBlob2));\n    layerEntries2 = ImmutableList.of();\n  }\n\n  @Test\n  public void testWithDirectory_existsButNotDirectory() throws IOException {\n    Path file = temporaryFolder.newFile().toPath();\n\n    try {\n      Cache.withDirectory(file);\n      Assert.fail();\n\n    } catch (CacheDirectoryCreationException ex) {\n      MatcherAssert.assertThat(\n          ex.getCause(), CoreMatchers.instanceOf(FileAlreadyExistsException.class));\n    }\n  }\n\n  @Test\n  public void testWriteCompressed_retrieveByLayerDigest()\n      throws IOException, CacheDirectoryCreationException, CacheCorruptedException {\n    Cache cache = Cache.withDirectory(temporaryFolder.newFolder().toPath());\n\n    verifyIsLayer1(cache.writeCompressedLayer(compress(layerBlob1)));\n    verifyIsLayer1(cache.retrieve(layerDigest1).orElseThrow(AssertionError::new));\n    Assert.assertFalse(cache.retrieve(layerDigest2).isPresent());\n  }\n\n  @Test\n  public void testWriteUncompressedWithLayerEntries_retrieveByLayerDigest()\n      throws IOException, CacheDirectoryCreationException, CacheCorruptedException {\n    Cache cache = Cache.withDirectory(temporaryFolder.newFolder().toPath());\n\n    verifyIsLayer1(cache.writeUncompressedLayer(layerBlob1, layerEntries1));\n    verifyIsLayer1(cache.retrieve(layerDigest1).orElseThrow(AssertionError::new));\n    Assert.assertFalse(cache.retrieve(layerDigest2).isPresent());\n  }\n\n  @Test\n  public void testWriteUncompressedWithLayerEntries_retrieveByLayerEntries()\n      throws IOException, CacheDirectoryCreationException, CacheCorruptedException {\n    Cache cache = Cache.withDirectory(temporaryFolder.newFolder().toPath());\n\n    verifyIsLayer1(cache.writeUncompressedLayer(layerBlob1, layerEntries1));\n    verifyIsLayer1(cache.retrieve(layerEntries1).orElseThrow(AssertionError::new));\n    Assert.assertFalse(cache.retrieve(layerDigest2).isPresent());\n\n    // A source file modification results in the cached layer to be out-of-date and not retrieved.\n    Files.setLastModifiedTime(\n        layerEntries1.get(0).getSourceFile(), FileTime.from(Instant.now().plusSeconds(1)));\n    Assert.assertFalse(cache.retrieve(layerEntries1).isPresent());\n  }\n\n  @Test\n  public void testRetrieveWithTwoEntriesInCache()\n      throws IOException, CacheDirectoryCreationException, CacheCorruptedException {\n    Cache cache = Cache.withDirectory(temporaryFolder.newFolder().toPath());\n\n    verifyIsLayer1(cache.writeUncompressedLayer(layerBlob1, layerEntries1));\n    verifyIsLayer2(cache.writeUncompressedLayer(layerBlob2, layerEntries2));\n    verifyIsLayer1(cache.retrieve(layerDigest1).orElseThrow(AssertionError::new));\n    verifyIsLayer2(cache.retrieve(layerDigest2).orElseThrow(AssertionError::new));\n    verifyIsLayer1(cache.retrieve(layerEntries1).orElseThrow(AssertionError::new));\n    verifyIsLayer2(cache.retrieve(layerEntries2).orElseThrow(AssertionError::new));\n  }\n\n  /**\n   * Verifies that {@code cachedLayer} corresponds to the first fake layer in {@link #setUp}.\n   *\n   * @param cachedLayer the {@link CachedLayer} to verify\n   * @throws IOException if an I/O exception occurs\n   */\n  private void verifyIsLayer1(CachedLayer cachedLayer) throws IOException {\n    Assert.assertEquals(\"layerBlob1\", Blobs.writeToString(decompress(cachedLayer.getBlob())));\n    Assert.assertEquals(layerDigest1, cachedLayer.getDigest());\n    Assert.assertEquals(layerDiffId1, cachedLayer.getDiffId());\n    Assert.assertEquals(layerSize1, cachedLayer.getSize());\n  }\n\n  /**\n   * Verifies that {@code cachedLayer} corresponds to the second fake layer in {@link #setUp}.\n   *\n   * @param cachedLayer the {@link CachedLayer} to verify\n   * @throws IOException if an I/O exception occurs\n   */\n  private void verifyIsLayer2(CachedLayer cachedLayer) throws IOException {\n    Assert.assertEquals(\"layerBlob2\", Blobs.writeToString(decompress(cachedLayer.getBlob())));\n    Assert.assertEquals(layerDigest2, cachedLayer.getDigest());\n    Assert.assertEquals(layerDiffId2, cachedLayer.getDiffId());\n    Assert.assertEquals(layerSize2, cachedLayer.getSize());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/CachedLayerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport java.io.IOException;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link CachedLayer}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class CachedLayerTest {\n\n  @Mock private DescriptorDigest mockLayerDigest;\n  @Mock private DescriptorDigest mockLayerDiffId;\n\n  @Test\n  public void testBuilder_fail() {\n    try {\n      CachedLayer.builder().build();\n      Assert.fail(\"missing required\");\n\n    } catch (NullPointerException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.containsString(\"layerDigest\"));\n    }\n\n    try {\n      CachedLayer.builder().setLayerDigest(mockLayerDigest).build();\n      Assert.fail(\"missing required\");\n\n    } catch (NullPointerException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.containsString(\"layerDiffId\"));\n    }\n\n    try {\n      CachedLayer.builder().setLayerDigest(mockLayerDigest).setLayerDiffId(mockLayerDiffId).build();\n      Assert.fail(\"missing required\");\n\n    } catch (NullPointerException ex) {\n      MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.containsString(\"layerBlob\"));\n    }\n  }\n\n  @Test\n  public void testBuilder_pass() throws IOException {\n    CachedLayer.Builder cachedLayerBuilder =\n        CachedLayer.builder()\n            .setLayerDigest(mockLayerDigest)\n            .setLayerDiffId(mockLayerDiffId)\n            .setLayerSize(1337);\n    Assert.assertFalse(cachedLayerBuilder.hasLayerBlob());\n    cachedLayerBuilder.setLayerBlob(Blobs.from(\"layerBlob\"));\n    Assert.assertTrue(cachedLayerBuilder.hasLayerBlob());\n    CachedLayer cachedLayer = cachedLayerBuilder.build();\n    Assert.assertEquals(mockLayerDigest, cachedLayer.getDigest());\n    Assert.assertEquals(mockLayerDiffId, cachedLayer.getDiffId());\n    Assert.assertEquals(1337, cachedLayer.getSize());\n    Assert.assertEquals(\"layerBlob\", Blobs.writeToString(cachedLayer.getBlob()));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/LayerEntriesSelectorTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.cache.LayerEntriesSelector.LayerEntryTemplate;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link LayerEntriesSelector}. */\npublic class LayerEntriesSelectorTest {\n\n  private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) {\n    return new FileEntry(\n        source,\n        destination,\n        FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination),\n        FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n  private ImmutableList<FileEntry> outOfOrderLayerEntries;\n  private ImmutableList<FileEntry> inOrderLayerEntries;\n\n  private static ImmutableList<LayerEntryTemplate> toLayerEntryTemplates(\n      ImmutableList<FileEntry> layerEntries) throws IOException {\n    ImmutableList.Builder<LayerEntryTemplate> builder = ImmutableList.builder();\n    for (FileEntry layerEntry : layerEntries) {\n      builder.add(new LayerEntryTemplate(layerEntry));\n    }\n    return builder.build();\n  }\n\n  @Before\n  public void setUp() throws IOException {\n    Path folder = temporaryFolder.newFolder().toPath();\n    Path file1 = Files.createDirectory(folder.resolve(\"files\"));\n    Path file2 = Files.createFile(folder.resolve(\"files\").resolve(\"two\"));\n    Path file3 = Files.createFile(folder.resolve(\"gile\"));\n\n    FileEntry testLayerEntry1 = defaultLayerEntry(file1, AbsoluteUnixPath.get(\"/extraction/path\"));\n    FileEntry testLayerEntry2 = defaultLayerEntry(file2, AbsoluteUnixPath.get(\"/extraction/path\"));\n    FileEntry testLayerEntry3 = defaultLayerEntry(file3, AbsoluteUnixPath.get(\"/extraction/path\"));\n    FileEntry testLayerEntry4 =\n        new FileEntry(\n            file3,\n            AbsoluteUnixPath.get(\"/extraction/path\"),\n            FilePermissions.fromOctalString(\"755\"),\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n    FileEntry testLayerEntry5 = defaultLayerEntry(file3, AbsoluteUnixPath.get(\"/extraction/patha\"));\n    FileEntry testLayerEntry6 =\n        new FileEntry(\n            file3,\n            AbsoluteUnixPath.get(\"/extraction/patha\"),\n            FilePermissions.fromOctalString(\"755\"),\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n\n    outOfOrderLayerEntries =\n        ImmutableList.of(\n            testLayerEntry4,\n            testLayerEntry2,\n            testLayerEntry6,\n            testLayerEntry3,\n            testLayerEntry1,\n            testLayerEntry5);\n    inOrderLayerEntries =\n        ImmutableList.of(\n            testLayerEntry1,\n            testLayerEntry2,\n            testLayerEntry3,\n            testLayerEntry4,\n            testLayerEntry5,\n            testLayerEntry6);\n  }\n\n  @Test\n  public void testLayerEntryTemplate_compareTo() throws IOException {\n    Assert.assertEquals(\n        toLayerEntryTemplates(inOrderLayerEntries),\n        ImmutableList.sortedCopyOf(toLayerEntryTemplates(outOfOrderLayerEntries)));\n  }\n\n  @Test\n  public void testToSortedJsonTemplates() throws IOException {\n    Assert.assertEquals(\n        toLayerEntryTemplates(inOrderLayerEntries),\n        LayerEntriesSelector.toSortedJsonTemplates(outOfOrderLayerEntries));\n  }\n\n  @Test\n  public void testGenerateSelector_empty() throws IOException {\n    DescriptorDigest expectedSelector = Digests.computeJsonDigest(ImmutableList.of());\n    Assert.assertEquals(\n        expectedSelector, LayerEntriesSelector.generateSelector(ImmutableList.of()));\n  }\n\n  @Test\n  public void testGenerateSelector() throws IOException {\n    DescriptorDigest expectedSelector =\n        Digests.computeJsonDigest(toLayerEntryTemplates(inOrderLayerEntries));\n    Assert.assertEquals(\n        expectedSelector, LayerEntriesSelector.generateSelector(outOfOrderLayerEntries));\n  }\n\n  @Test\n  public void testGenerateSelector_sourceModificationTimeChanged() throws IOException {\n    Path layerFile = temporaryFolder.newFile().toPath();\n    Files.setLastModifiedTime(layerFile, FileTime.from(Instant.EPOCH));\n    FileEntry layerEntry = defaultLayerEntry(layerFile, AbsoluteUnixPath.get(\"/extraction/path\"));\n    DescriptorDigest expectedSelector =\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry));\n\n    // Verify that changing source modification time generates a different selector\n    Files.setLastModifiedTime(layerFile, FileTime.from(Instant.ofEpochSecond(1)));\n    Assert.assertNotEquals(\n        expectedSelector, LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry)));\n\n    // Verify that changing source modification time back generates same selector\n    Files.setLastModifiedTime(layerFile, FileTime.from(Instant.EPOCH));\n    Assert.assertEquals(\n        expectedSelector, LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry)));\n  }\n\n  @Test\n  public void testGenerateSelector_targetModificationTimeChanged() throws IOException {\n    Path layerFile = temporaryFolder.newFile().toPath();\n    AbsoluteUnixPath pathInContainer = AbsoluteUnixPath.get(\"/bar\");\n    FilePermissions permissions = FilePermissions.fromOctalString(\"111\");\n\n    FileEntry layerEntry1 = new FileEntry(layerFile, pathInContainer, permissions, Instant.now());\n    FileEntry layerEntry2 = new FileEntry(layerFile, pathInContainer, permissions, Instant.EPOCH);\n\n    // Verify that different target modification times generate different selectors\n    Assert.assertNotEquals(\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry1)),\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry2)));\n  }\n\n  @Test\n  public void testGenerateSelector_permissionsModified() throws IOException {\n    Path layerFile = temporaryFolder.newFolder(\"testFolder\").toPath().resolve(\"file\");\n    Files.write(layerFile, \"hello\".getBytes(StandardCharsets.UTF_8));\n    FileEntry layerEntry111 =\n        new FileEntry(\n            layerFile,\n            AbsoluteUnixPath.get(\"/extraction/path\"),\n            FilePermissions.fromOctalString(\"111\"),\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n    FileEntry layerEntry222 =\n        new FileEntry(\n            layerFile,\n            AbsoluteUnixPath.get(\"/extraction/path\"),\n            FilePermissions.fromOctalString(\"222\"),\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n\n    // Verify that changing permissions generates a different selector\n    Assert.assertNotEquals(\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry111)),\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry222)));\n  }\n\n  @Test\n  public void testGenerateSelector_ownersModified() throws IOException {\n    Path layerFile = temporaryFolder.newFolder(\"testFolder\").toPath().resolve(\"file\");\n    Files.write(layerFile, \"hello\".getBytes(StandardCharsets.UTF_8));\n    FileEntry layerEntry111 =\n        new FileEntry(\n            layerFile,\n            AbsoluteUnixPath.get(\"/extraction/path\"),\n            FilePermissions.fromOctalString(\"111\"),\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME,\n            \"0:0\");\n    FileEntry layerEntry222 =\n        new FileEntry(\n            layerFile,\n            AbsoluteUnixPath.get(\"/extraction/path\"),\n            FilePermissions.fromOctalString(\"222\"),\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME,\n            \"foouser\");\n\n    // Verify that changing ownership generates a different selector\n    Assert.assertNotEquals(\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry111)),\n        LayerEntriesSelector.generateSelector(ImmutableList.of(layerEntry222)));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/cache/RetryTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.cache;\n\nimport java.util.concurrent.TimeUnit;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Retry}. */\npublic class RetryTest {\n  private int actionCount = 0;\n\n  private boolean successfulAction() {\n    ++actionCount;\n    return true;\n  }\n\n  private boolean unsuccessfulAction() {\n    ++actionCount;\n    return false;\n  }\n\n  private boolean exceptionAction() throws Exception {\n    ++actionCount;\n    throw new Exception(\"whee\");\n  }\n\n  @Test\n  public void testSuccessfulAction() throws Exception {\n    boolean result = Retry.action(this::successfulAction).run();\n    Assert.assertTrue(result);\n    Assert.assertEquals(1, actionCount);\n  }\n\n  @Test\n  public void testMaximumRetries_default() throws Exception {\n    boolean result = Retry.action(this::unsuccessfulAction).run();\n    Assert.assertFalse(result);\n    Assert.assertEquals(5, actionCount);\n  }\n\n  @Test\n  public void testMaximumRetries_specified() throws Exception {\n    boolean result = Retry.action(this::unsuccessfulAction).maximumRetries(2).run();\n    Assert.assertFalse(result);\n    Assert.assertEquals(2, actionCount);\n  }\n\n  @Test\n  public void testRetryableException() {\n    // all exceptions are retryable by default, so should retry 5 times\n    try {\n      Retry.action(this::exceptionAction).run();\n      Assert.fail(\"should have thrown exception\");\n    } catch (Exception ex) {\n      Assert.assertEquals(\"whee\", ex.getMessage());\n      Assert.assertEquals(5, actionCount);\n    }\n  }\n\n  @Test\n  public void testNonRetryableException() {\n    // the exception is not ok and so should only try 1 time\n    try {\n      Retry.action(this::exceptionAction).retryOnException(ex -> false).run();\n      Assert.fail(\"should have thrown exception\");\n    } catch (Exception ex) {\n      Assert.assertEquals(\"whee\", ex.getMessage());\n      Assert.assertEquals(1, actionCount);\n    }\n  }\n\n  @Test\n  public void testInterruptSleep() throws Exception {\n    // interrupt the current thread so as to cause the retry's sleep() to throw\n    // an InterruptedException\n    Thread.currentThread().interrupt();\n    try {\n      boolean result = Retry.action(this::unsuccessfulAction).sleep(10, TimeUnit.SECONDS).run();\n      Assert.assertFalse(result);\n      Assert.assertEquals(1, actionCount);\n    } finally {\n      // This thread should be marked as interrupted (plus clear the flag for the test)\n      Assert.assertTrue(Thread.interrupted());\n    }\n  }\n\n  @Test\n  public void testInvalid_maximumRetries() {\n    try {\n      Retry.action(this::successfulAction).maximumRetries(0);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      /* maximumRetries() ensures the retry value is at least 1. */\n    }\n  }\n\n  @Test\n  public void testInvalid_sleep() {\n    try {\n      Retry.action(this::successfulAction).sleep(-1, TimeUnit.DAYS);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      /* sleep() ensures the sleep value is non-negative. */\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/configuration/BuildContextTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.ListMultimap;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.mockito.Mockito;\n\n/** Tests for {@link BuildContext}. */\npublic class BuildContextTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  private static BuildContext.Builder createBasicTestBuilder() {\n    return BuildContext.builder()\n        .setBaseImageConfiguration(\n            ImageConfiguration.builder(Mockito.mock(ImageReference.class)).build())\n        .setTargetImageConfiguration(\n            ImageConfiguration.builder(Mockito.mock(ImageReference.class)).build())\n        .setContainerConfiguration(ContainerConfiguration.builder().build())\n        .setBaseImageLayersCacheDirectory(Paths.get(\"ignored\"))\n        .setApplicationLayersCacheDirectory(Paths.get(\"ignored\"));\n  }\n\n  @Test\n  public void testBuilder() throws Exception {\n    String expectedBaseImageServerUrl = \"someserver\";\n    String expectedBaseImageName = \"baseimage\";\n    String expectedBaseImageTag = \"baseimagetag\";\n    String expectedTargetServerUrl = \"someotherserver\";\n    String expectedTargetImageName = \"targetimage\";\n    String expectedTargetTag = \"targettag\";\n    Set<String> additionalTargetImageTags = ImmutableSet.of(\"tag1\", \"tag2\", \"tag3\");\n    Set<String> expectedTargetImageTags = ImmutableSet.of(\"targettag\", \"tag1\", \"tag2\", \"tag3\");\n    List<CredentialRetriever> credentialRetrievers =\n        Collections.singletonList(() -> Optional.of(Credential.from(\"username\", \"password\")));\n    Instant expectedCreationTime = Instant.ofEpochSecond(10000);\n    List<String> expectedEntrypoint = Arrays.asList(\"some\", \"entrypoint\");\n    List<String> expectedProgramArguments = Arrays.asList(\"arg1\", \"arg2\");\n    Map<String, String> expectedEnvironment = ImmutableMap.of(\"key\", \"value\");\n    Set<Port> expectedExposedPorts = ImmutableSet.of(Port.tcp(1000), Port.tcp(2000));\n    Map<String, String> expectedLabels = ImmutableMap.of(\"key1\", \"value1\", \"key2\", \"value2\");\n    Class<? extends BuildableManifestTemplate> expectedTargetFormat = OciManifestTemplate.class;\n    Path expectedApplicationLayersCacheDirectory = Paths.get(\"application/layers\");\n    Path expectedBaseImageLayersCacheDirectory = Paths.get(\"base/image/layers\");\n    List<FileEntriesLayer> expectedLayerConfigurations =\n        Collections.singletonList(\n            FileEntriesLayer.builder()\n                .addEntry(Paths.get(\"sourceFile\"), AbsoluteUnixPath.get(\"/path/in/container\"))\n                .build());\n    String expectedCreatedBy = \"createdBy\";\n    ListMultimap<String, String> expectedRegistryMirrors =\n        ImmutableListMultimap.of(\"some.registry\", \"mirror1\", \"some.registry\", \"mirror2\");\n\n    ImageConfiguration baseImageConfiguration =\n        ImageConfiguration.builder(\n                ImageReference.of(\n                    expectedBaseImageServerUrl, expectedBaseImageName, expectedBaseImageTag))\n            .build();\n    ImageConfiguration targetImageConfiguration =\n        ImageConfiguration.builder(\n                ImageReference.of(\n                    expectedTargetServerUrl, expectedTargetImageName, expectedTargetTag))\n            .setCredentialRetrievers(credentialRetrievers)\n            .build();\n    ContainerConfiguration containerConfiguration =\n        ContainerConfiguration.builder()\n            .setCreationTime(expectedCreationTime)\n            .setEntrypoint(expectedEntrypoint)\n            .setProgramArguments(expectedProgramArguments)\n            .setEnvironment(expectedEnvironment)\n            .setExposedPorts(expectedExposedPorts)\n            .setLabels(expectedLabels)\n            .build();\n    BuildContext.Builder buildContextBuilder =\n        BuildContext.builder()\n            .setBaseImageConfiguration(baseImageConfiguration)\n            .setTargetImageConfiguration(targetImageConfiguration)\n            .setAdditionalTargetImageTags(additionalTargetImageTags)\n            .setContainerConfiguration(containerConfiguration)\n            .setApplicationLayersCacheDirectory(expectedApplicationLayersCacheDirectory)\n            .setBaseImageLayersCacheDirectory(expectedBaseImageLayersCacheDirectory)\n            .setTargetFormat(ImageFormat.OCI)\n            .setEnablePlatformTags(true)\n            .setAllowInsecureRegistries(true)\n            .setLayerConfigurations(expectedLayerConfigurations)\n            .setToolName(expectedCreatedBy)\n            .setRegistryMirrors(expectedRegistryMirrors);\n    BuildContext buildContext = buildContextBuilder.build();\n\n    Assert.assertEquals(\n        expectedCreationTime, buildContext.getContainerConfiguration().getCreationTime());\n    Assert.assertEquals(\n        expectedBaseImageServerUrl, buildContext.getBaseImageConfiguration().getImageRegistry());\n    Assert.assertEquals(\n        expectedBaseImageName, buildContext.getBaseImageConfiguration().getImageRepository());\n    Assert.assertEquals(\n        expectedBaseImageTag, buildContext.getBaseImageConfiguration().getImageQualifier());\n    Assert.assertEquals(\n        expectedTargetServerUrl, buildContext.getTargetImageConfiguration().getImageRegistry());\n    Assert.assertEquals(\n        expectedTargetImageName, buildContext.getTargetImageConfiguration().getImageRepository());\n    Assert.assertEquals(\n        expectedTargetTag, buildContext.getTargetImageConfiguration().getImageQualifier());\n    Assert.assertEquals(expectedTargetImageTags, buildContext.getAllTargetImageTags());\n    Assert.assertEquals(\n        Credential.from(\"username\", \"password\"),\n        buildContext\n            .getTargetImageConfiguration()\n            .getCredentialRetrievers()\n            .get(0)\n            .retrieve()\n            .orElseThrow(AssertionError::new));\n    Assert.assertEquals(\n        expectedProgramArguments, buildContext.getContainerConfiguration().getProgramArguments());\n    Assert.assertEquals(\n        expectedEnvironment, buildContext.getContainerConfiguration().getEnvironmentMap());\n    Assert.assertEquals(\n        expectedExposedPorts, buildContext.getContainerConfiguration().getExposedPorts());\n    Assert.assertEquals(expectedLabels, buildContext.getContainerConfiguration().getLabels());\n    Assert.assertEquals(expectedTargetFormat, buildContext.getTargetFormat());\n    Assert.assertEquals(\n        expectedApplicationLayersCacheDirectory,\n        buildContextBuilder.getApplicationLayersCacheDirectory());\n    Assert.assertEquals(\n        expectedBaseImageLayersCacheDirectory,\n        buildContextBuilder.getBaseImageLayersCacheDirectory());\n    Assert.assertEquals(expectedLayerConfigurations, buildContext.getLayerConfigurations());\n    Assert.assertEquals(\n        expectedEntrypoint, buildContext.getContainerConfiguration().getEntrypoint());\n    Assert.assertEquals(expectedCreatedBy, buildContext.getToolName());\n    Assert.assertEquals(expectedRegistryMirrors, buildContext.getRegistryMirrors());\n    Assert.assertNotNull(buildContext.getExecutorService());\n    Assert.assertTrue(buildContext.getEnablePlatformTags());\n  }\n\n  @Test\n  public void testBuilder_default() throws CacheDirectoryCreationException {\n    // These are required and don't have defaults.\n    String expectedBaseImageServerUrl = \"someserver\";\n    String expectedBaseImageName = \"baseimage\";\n    String expectedBaseImageTag = \"baseimagetag\";\n    String expectedTargetServerUrl = \"someotherserver\";\n    String expectedTargetImageName = \"targetimage\";\n    String expectedTargetTag = \"targettag\";\n\n    ImageConfiguration baseImageConfiguration =\n        ImageConfiguration.builder(\n                ImageReference.of(\n                    expectedBaseImageServerUrl, expectedBaseImageName, expectedBaseImageTag))\n            .build();\n    ImageConfiguration targetImageConfiguration =\n        ImageConfiguration.builder(\n                ImageReference.of(\n                    expectedTargetServerUrl, expectedTargetImageName, expectedTargetTag))\n            .build();\n    BuildContext.Builder buildContextBuilder =\n        BuildContext.builder()\n            .setBaseImageConfiguration(baseImageConfiguration)\n            .setTargetImageConfiguration(targetImageConfiguration)\n            .setContainerConfiguration(ContainerConfiguration.builder().setUser(\"12345\").build())\n            .setBaseImageLayersCacheDirectory(Paths.get(\"ignored\"))\n            .setApplicationLayersCacheDirectory(Paths.get(\"ignored\"));\n    BuildContext buildContext = buildContextBuilder.build();\n\n    Assert.assertEquals(ImmutableSet.of(\"targettag\"), buildContext.getAllTargetImageTags());\n    Assert.assertEquals(V22ManifestTemplate.class, buildContext.getTargetFormat());\n    Assert.assertNotNull(buildContextBuilder.getApplicationLayersCacheDirectory());\n    Assert.assertEquals(\n        Paths.get(\"ignored\"), buildContextBuilder.getApplicationLayersCacheDirectory());\n    Assert.assertNotNull(buildContextBuilder.getBaseImageLayersCacheDirectory());\n    Assert.assertEquals(\n        Paths.get(\"ignored\"), buildContextBuilder.getBaseImageLayersCacheDirectory());\n    Assert.assertEquals(\"12345\", buildContext.getContainerConfiguration().getUser());\n    Assert.assertEquals(Collections.emptyList(), buildContext.getLayerConfigurations());\n    Assert.assertEquals(\"jib\", buildContext.getToolName());\n    Assert.assertEquals(0, buildContext.getRegistryMirrors().size());\n    Assert.assertFalse(buildContext.getEnablePlatformTags());\n  }\n\n  @Test\n  public void testBuilder_missingValues() throws CacheDirectoryCreationException {\n    // Target image is missing\n    try {\n      BuildContext.builder()\n          .setBaseImageConfiguration(\n              ImageConfiguration.builder(Mockito.mock(ImageReference.class)).build())\n          .setBaseImageLayersCacheDirectory(Paths.get(\"ignored\"))\n          .setContainerConfiguration(ContainerConfiguration.builder().build())\n          .setApplicationLayersCacheDirectory(Paths.get(\"ignored\"))\n          .build();\n      Assert.fail(\"BuildContext should not be built with missing values\");\n\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\"target image configuration is required but not set\", ex.getMessage());\n    }\n\n    // Two required fields missing\n    try {\n      BuildContext.builder()\n          .setBaseImageLayersCacheDirectory(Paths.get(\"ignored\"))\n          .setApplicationLayersCacheDirectory(Paths.get(\"ignored\"))\n          .setContainerConfiguration(ContainerConfiguration.builder().build())\n          .build();\n      Assert.fail(\"BuildContext should not be built with missing values\");\n\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\n          \"base image configuration and target image configuration are required but not set\",\n          ex.getMessage());\n    }\n\n    // All required fields missing\n    try {\n      BuildContext.builder().build();\n      Assert.fail(\"BuildContext should not be built with missing values\");\n\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\n          \"base image configuration, target image configuration, container configuration, base \"\n              + \"image layers cache directory, and application layers cache directory are required \"\n              + \"but not set\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuilder_digestWarning()\n      throws CacheDirectoryCreationException, InvalidImageReferenceException {\n    EventHandlers mockEventHandlers = Mockito.mock(EventHandlers.class);\n    BuildContext.Builder builder = createBasicTestBuilder().setEventHandlers(mockEventHandlers);\n\n    builder\n        .setBaseImageConfiguration(\n            ImageConfiguration.builder(\n                    ImageReference.parse(\n                        \"image@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"))\n                .build())\n        .build();\n    Mockito.verify(mockEventHandlers, Mockito.never()).dispatch(LogEvent.warn(Mockito.anyString()));\n\n    builder\n        .setBaseImageConfiguration(\n            ImageConfiguration.builder(ImageReference.parse(\"image:tag\")).build())\n        .build();\n    Mockito.verify(mockEventHandlers)\n        .dispatch(\n            LogEvent.warn(\n                \"Base image 'image:tag' does not use a specific image digest - build may not be reproducible\"));\n  }\n\n  @Test\n  public void testClose_shutDownInternalExecutorService()\n      throws IOException, CacheDirectoryCreationException {\n\n    BuildContext buildContext = createBasicTestBuilder().build();\n    buildContext.close();\n\n    Assert.assertTrue(buildContext.getExecutorService().isShutdown());\n  }\n\n  @Test\n  public void testClose_doNotShutDownProvidedExecutorService()\n      throws IOException, CacheDirectoryCreationException {\n    ExecutorService executorService = MoreExecutors.newDirectExecutorService();\n    BuildContext buildContext =\n        createBasicTestBuilder().setExecutorService(executorService).build();\n    buildContext.close();\n\n    Assert.assertSame(executorService, buildContext.getExecutorService());\n    Assert.assertFalse(buildContext.getExecutorService().isShutdown());\n  }\n\n  @Test\n  public void testGetUserAgent_unset() throws CacheDirectoryCreationException {\n    BuildContext buildContext = createBasicTestBuilder().build();\n\n    String generatedUserAgent = buildContext.makeUserAgent();\n\n    Assert.assertEquals(\"jib null jib\", generatedUserAgent);\n  }\n\n  @Test\n  public void testGetUserAgent_withValues() throws CacheDirectoryCreationException {\n    BuildContext buildContext =\n        createBasicTestBuilder().setToolName(\"test-name\").setToolVersion(\"test-version\").build();\n\n    String generatedUserAgent = buildContext.makeUserAgent();\n\n    Assert.assertEquals(\"jib test-version test-name\", generatedUserAgent);\n  }\n\n  @Test\n  public void testGetUserAgentWithUpstreamClient() throws CacheDirectoryCreationException {\n    System.setProperty(JibSystemProperties.UPSTREAM_CLIENT, \"skaffold/0.34.0\");\n    BuildContext buildContext =\n        createBasicTestBuilder().setToolName(\"test-name\").setToolVersion(\"test-version\").build();\n\n    String generatedUserAgent = buildContext.makeUserAgent();\n\n    Assert.assertEquals(\"jib test-version test-name skaffold/0.34.0\", generatedUserAgent);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/configuration/ContainerConfigurationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Hashtable;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ContainerConfiguration}. */\npublic class ContainerConfigurationTest {\n\n  @Test\n  public void testBuilder_nullValues() {\n    // Java arguments element should not be null.\n    try {\n      ContainerConfiguration.builder().setProgramArguments(Arrays.asList(\"first\", null));\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"program arguments list contains null elements\", ex.getMessage());\n    }\n\n    // Entrypoint element should not be null.\n    try {\n      ContainerConfiguration.builder().setEntrypoint(Arrays.asList(\"first\", null));\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"entrypoint contains null elements\", ex.getMessage());\n    }\n\n    // Exposed ports element should not be null.\n    Set<Port> badPorts = new HashSet<>(Arrays.asList(Port.tcp(1000), null));\n    try {\n      ContainerConfiguration.builder().setExposedPorts(badPorts);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"ports list contains null elements\", ex.getMessage());\n    }\n\n    // Volume element should not be null.\n    Set<AbsoluteUnixPath> badVolumes =\n        new HashSet<>(Arrays.asList(AbsoluteUnixPath.get(\"/\"), null));\n    try {\n      ContainerConfiguration.builder().setVolumes(badVolumes);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"volumes list contains null elements\", ex.getMessage());\n    }\n\n    Map<String, String> nullKeyMap = new HashMap<>();\n    nullKeyMap.put(null, \"value\");\n    Map<String, String> nullValueMap = new HashMap<>();\n    nullValueMap.put(\"key\", null);\n\n    // Label keys should not be null.\n    try {\n      ContainerConfiguration.builder().setLabels(nullKeyMap);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"labels map contains null keys\", ex.getMessage());\n    }\n\n    // Labels values should not be null.\n    try {\n      ContainerConfiguration.builder().setLabels(nullValueMap);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"labels map contains null values\", ex.getMessage());\n    }\n\n    // Environment keys should not be null.\n    try {\n      ContainerConfiguration.builder().setEnvironment(nullKeyMap);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"environment map contains null keys\", ex.getMessage());\n    }\n\n    // Environment values should not be null.\n    try {\n      ContainerConfiguration.builder().setEnvironment(nullValueMap);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"environment map contains null values for key(s): key\", ex.getMessage());\n    }\n  }\n\n  @Test\n  @SuppressWarnings(\"JdkObsolete\") // for hashtable\n  public void testBuilder_environmentMapTypes() {\n    // Can accept empty environment.\n    Assert.assertNotNull(\n        ContainerConfiguration.builder().setEnvironment(ImmutableMap.of()).build());\n    // Can handle other map types (https://github.com/GoogleContainerTools/jib/issues/632)\n    Assert.assertNotNull(ContainerConfiguration.builder().setEnvironment(new TreeMap<>()));\n    Assert.assertNotNull(ContainerConfiguration.builder().setEnvironment(new Hashtable<>()));\n  }\n\n  @Test\n  public void testBuilder_user() {\n    ContainerConfiguration configuration = ContainerConfiguration.builder().setUser(\"john\").build();\n    Assert.assertEquals(\"john\", configuration.getUser());\n  }\n\n  @Test\n  public void testBuilder_workingDirectory() {\n    ContainerConfiguration configuration =\n        ContainerConfiguration.builder().setWorkingDirectory(AbsoluteUnixPath.get(\"/path\")).build();\n    Assert.assertEquals(AbsoluteUnixPath.get(\"/path\"), configuration.getWorkingDirectory());\n  }\n\n  @Test\n  public void testSetPlatforms_emptySet() {\n    try {\n      ContainerConfiguration.builder().setPlatforms(Collections.emptySet()).build();\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"platforms set cannot be empty\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testAddPlatform_duplicatePlatforms() {\n    ContainerConfiguration configuration =\n        ContainerConfiguration.builder()\n            .addPlatform(\"testArchitecture\", \"testOS\")\n            .addPlatform(\"testArchitecture\", \"testOS\")\n            .build();\n    Assert.assertEquals(\n        ImmutableSet.of(new Platform(\"amd64\", \"linux\"), new Platform(\"testArchitecture\", \"testOS\")),\n        configuration.getPlatforms());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/configuration/DockerHealthCheckTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.configuration;\n\nimport com.google.common.collect.ImmutableList;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link DockerHealthCheck}. */\npublic class DockerHealthCheckTest {\n\n  @Test\n  public void testBuild() {\n    DockerHealthCheck healthCheck =\n        DockerHealthCheck.fromCommand(ImmutableList.of(\"echo\", \"hi\"))\n            .setInterval(Duration.ofNanos(123))\n            .setTimeout(Duration.ofNanos(456))\n            .setStartPeriod(Duration.ofNanos(789))\n            .setRetries(10)\n            .build();\n\n    Assert.assertTrue(healthCheck.getInterval().isPresent());\n    Assert.assertEquals(Duration.ofNanos(123), healthCheck.getInterval().get());\n    Assert.assertTrue(healthCheck.getTimeout().isPresent());\n    Assert.assertEquals(Duration.ofNanos(456), healthCheck.getTimeout().get());\n    Assert.assertTrue(healthCheck.getStartPeriod().isPresent());\n    Assert.assertEquals(Duration.ofNanos(789), healthCheck.getStartPeriod().get());\n    Assert.assertTrue(healthCheck.getRetries().isPresent());\n    Assert.assertEquals(10, (int) healthCheck.getRetries().get());\n  }\n\n  @Test\n  public void testBuild_invalidCommand() {\n    try {\n      DockerHealthCheck.fromCommand(ImmutableList.of());\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"command must not be empty\", ex.getMessage());\n    }\n\n    try {\n      DockerHealthCheck.fromCommand(Arrays.asList(\"CMD\", null));\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"command must not contain null elements\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/docker/AnotherDockerClient.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.docker;\n\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.ImageDetails;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\npublic class AnotherDockerClient implements DockerClient {\n  @Override\n  public boolean supported(Map<String, String> parameters) {\n    return parameters.containsKey(\"test\");\n  }\n\n  @Override\n  public String load(ImageTarball imageTarball, Consumer<Long> writtenByteCountListener)\n      throws InterruptedException, IOException {\n    return null;\n  }\n\n  @Override\n  public void save(\n      ImageReference imageReference, Path outputPath, Consumer<Long> writtenByteCountListener)\n      throws InterruptedException, IOException {}\n\n  @Override\n  public ImageDetails inspect(ImageReference imageReference)\n      throws IOException, InterruptedException {\n    return null;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.docker;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.DockerInfoDetails;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.docker.CliDockerClient.DockerImageDetails;\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.AdditionalAnswers;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\nimport org.mockito.stubbing.VoidAnswer1;\n\n/** Tests for {@link CliDockerClient}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class CliDockerClientTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock private ProcessBuilder mockProcessBuilder;\n  @Mock private Process mockProcess;\n  @Mock private ImageTarball imageTarball;\n\n  @Before\n  public void setUp() throws IOException {\n    Mockito.when(mockProcessBuilder.start()).thenReturn(mockProcess);\n    Mockito.doAnswer(\n            AdditionalAnswers.answerVoid(\n                (VoidAnswer1<OutputStream>)\n                    out -> out.write(\"jib\".getBytes(StandardCharsets.UTF_8))))\n        .when(imageTarball)\n        .writeTo(Mockito.any(OutputStream.class));\n  }\n\n  @Test\n  public void testIsDockerInstalled_fail() {\n    Assert.assertFalse(CliDockerClient.isDockerInstalled(Paths.get(\"path/to/nonexistent/file\")));\n  }\n\n  @Test\n  public void testIsDockerInstalled_pass() throws URISyntaxException {\n    Assert.assertTrue(\n        CliDockerClient.isDockerInstalled(\n            Paths.get(Resources.getResource(\"core/docker/emptyFile\").toURI())));\n  }\n\n  @Test\n  public void testInfo() throws InterruptedException, IOException {\n    String dockerInfoJson = \"{ \\\"OSType\\\": \\\"windows\\\",\" + \"\\\"Architecture\\\": \\\"arm64\\\"}\";\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              assertThat(subcommand).containsExactly(\"info\", \"-f\", \"{{json .}}\");\n              return mockProcessBuilder;\n            });\n    // Simulates stdout.\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(new ByteArrayInputStream(dockerInfoJson.getBytes()));\n\n    DockerInfoDetails infoDetails = testDockerClient.info();\n    assertThat(infoDetails.getArchitecture()).isEqualTo(\"arm64\");\n    assertThat(infoDetails.getOsType()).isEqualTo(\"windows\");\n  }\n\n  @Test\n  public void testInfo_fail() throws InterruptedException {\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              assertThat(subcommand).containsExactly(\"info\", \"-f\", \"{{json .}}\");\n              return mockProcessBuilder;\n            });\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(new ByteArrayInputStream(\"\".getBytes(StandardCharsets.UTF_8)));\n    Mockito.when(mockProcess.waitFor()).thenReturn(1);\n    Mockito.when(mockProcess.getErrorStream())\n        .thenReturn(new ByteArrayInputStream(\"error\".getBytes(StandardCharsets.UTF_8)));\n\n    IOException exception = assertThrows(IOException.class, testDockerClient::info);\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"'docker info' command failed with error: error\");\n  }\n\n  @Test\n  public void testInfo_stdinFail() throws InterruptedException {\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              assertThat(subcommand).containsExactly(\"info\", \"-f\", \"{{json .}}\");\n              return mockProcessBuilder;\n            });\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(\n            new InputStream() {\n\n              @Override\n              public int read() throws IOException {\n                throw new IOException();\n              }\n            });\n\n    assertThrows(IOException.class, testDockerClient::info);\n\n    Mockito.verify(mockProcess, Mockito.never()).waitFor();\n  }\n\n  @Test\n  public void testInfo_returnsUnknownKeys() throws InterruptedException, IOException {\n    String dockerInfoJson = \"{ \\\"unknownOS\\\": \\\"windows\\\",\" + \"\\\"unknownArchitecture\\\": \\\"arm64\\\"}\";\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              assertThat(subcommand).containsExactly(\"info\", \"-f\", \"{{json .}}\");\n              return mockProcessBuilder;\n            });\n    Mockito.when(mockProcess.waitFor()).thenReturn(0);\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(new ByteArrayInputStream(dockerInfoJson.getBytes()));\n\n    DockerInfoDetails infoDetails = testDockerClient.info();\n    assertThat(infoDetails.getArchitecture()).isEmpty();\n    assertThat(infoDetails.getOsType()).isEmpty();\n  }\n\n  @Test\n  public void testLoad() throws IOException, InterruptedException {\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              Assert.assertEquals(Collections.singletonList(\"load\"), subcommand);\n              return mockProcessBuilder;\n            });\n    Mockito.when(mockProcess.waitFor()).thenReturn(0);\n\n    // Captures stdin.\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    Mockito.when(mockProcess.getOutputStream()).thenReturn(byteArrayOutputStream);\n\n    // Simulates stdout.\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(new ByteArrayInputStream(\"output\".getBytes(StandardCharsets.UTF_8)));\n\n    String output = testDockerClient.load(imageTarball, ignored -> {});\n\n    Assert.assertEquals(\n        \"jib\", new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8));\n    Assert.assertEquals(\"output\", output);\n  }\n\n  @Test\n  public void testLoad_stdinFail() throws InterruptedException {\n    DockerClient testDockerClient = new CliDockerClient(ignored -> mockProcessBuilder);\n\n    Mockito.when(mockProcess.getOutputStream())\n        .thenReturn(\n            new OutputStream() {\n\n              @Override\n              public void write(int b) throws IOException {\n                throw new IOException();\n              }\n            });\n    Mockito.when(mockProcess.getErrorStream())\n        .thenReturn(new ByteArrayInputStream(\"error\".getBytes(StandardCharsets.UTF_8)));\n\n    try {\n      testDockerClient.load(imageTarball, ignored -> {});\n      Assert.fail(\"Write should have failed\");\n\n    } catch (IOException ex) {\n      Assert.assertEquals(\"'docker load' command failed with error: error\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testLoad_stdinFail_stderrFail() throws InterruptedException {\n    DockerClient testDockerClient = new CliDockerClient(ignored -> mockProcessBuilder);\n\n    Mockito.when(mockProcess.getOutputStream())\n        .thenReturn(\n            new OutputStream() {\n\n              @Override\n              public void write(int b) throws IOException {\n                throw new IOException(\"I/O failed\");\n              }\n            });\n    Mockito.when(mockProcess.getErrorStream())\n        .thenReturn(\n            new InputStream() {\n\n              @Override\n              public int read() throws IOException {\n                throw new IOException();\n              }\n            });\n\n    try {\n      testDockerClient.load(imageTarball, ignored -> {});\n      Assert.fail(\"Write should have failed\");\n\n    } catch (IOException ex) {\n      Assert.assertEquals(\"'docker load' command failed with error: I/O failed\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testLoad_stdoutFail() throws InterruptedException {\n    DockerClient testDockerClient = new CliDockerClient(ignored -> mockProcessBuilder);\n    Mockito.when(mockProcess.waitFor()).thenReturn(1);\n\n    Mockito.when(mockProcess.getOutputStream()).thenReturn(ByteStreams.nullOutputStream());\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(new ByteArrayInputStream(\"ignored\".getBytes(StandardCharsets.UTF_8)));\n    Mockito.when(mockProcess.getErrorStream())\n        .thenReturn(new ByteArrayInputStream(\"error\".getBytes(StandardCharsets.UTF_8)));\n\n    try {\n      testDockerClient.load(imageTarball, ignored -> {});\n      Assert.fail(\"Process should have failed\");\n\n    } catch (IOException ex) {\n      Assert.assertEquals(\"'docker load' command failed with error: error\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testSave() throws InterruptedException, IOException {\n    DockerClient testDockerClient = makeDockerSaveClient();\n    Mockito.when(mockProcess.waitFor()).thenReturn(0);\n\n    long[] counter = new long[1];\n    testDockerClient.save(\n        ImageReference.of(null, \"testimage\", null),\n        temporaryFolder.getRoot().toPath().resolve(\"out.tar\"),\n        bytes -> counter[0] += bytes);\n\n    // InputStream writes \"jib\", so 3 bytes of progress should have been counted.\n    Assert.assertEquals(3, counter[0]);\n  }\n\n  @Test\n  public void testSave_fail() throws InterruptedException {\n    DockerClient testDockerClient = makeDockerSaveClient();\n    Mockito.when(mockProcess.waitFor()).thenReturn(1);\n\n    Mockito.when(mockProcess.getErrorStream())\n        .thenReturn(new ByteArrayInputStream(\"error\".getBytes(StandardCharsets.UTF_8)));\n\n    try {\n      testDockerClient.save(\n          ImageReference.of(null, \"testimage\", null),\n          temporaryFolder.getRoot().toPath().resolve(\"out.tar\"),\n          ignored -> {});\n      Assert.fail(\"docker save should have failed\");\n\n    } catch (IOException ex) {\n      Assert.assertEquals(\"'docker save' command failed with error: error\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testDefaultProcessorBuilderFactory_customExecutable() {\n    ProcessBuilder processBuilder =\n        CliDockerClient.defaultProcessBuilderFactory(\"docker-executable\", ImmutableMap.of())\n            .apply(Arrays.asList(\"sub\", \"command\"));\n\n    Assert.assertEquals(\n        Arrays.asList(\"docker-executable\", \"sub\", \"command\"), processBuilder.command());\n    Assert.assertEquals(System.getenv(), processBuilder.environment());\n  }\n\n  @Test\n  public void testDefaultProcessorBuilderFactory_customEnvironment() {\n    ImmutableMap<String, String> environment = ImmutableMap.of(\"Key1\", \"Value1\");\n\n    Map<String, String> expectedEnvironment = new HashMap<>(System.getenv());\n    expectedEnvironment.putAll(environment);\n\n    ProcessBuilder processBuilder =\n        CliDockerClient.defaultProcessBuilderFactory(\"docker\", environment)\n            .apply(Collections.emptyList());\n\n    Assert.assertEquals(expectedEnvironment, processBuilder.environment());\n  }\n\n  @Test\n  public void testSize_fail() throws InterruptedException {\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              Assert.assertEquals(\"inspect\", subcommand.get(0));\n              return mockProcessBuilder;\n            });\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(new ByteArrayInputStream(\"\".getBytes(StandardCharsets.UTF_8)));\n    Mockito.when(mockProcess.waitFor()).thenReturn(1);\n\n    Mockito.when(mockProcess.getErrorStream())\n        .thenReturn(new ByteArrayInputStream(\"error\".getBytes(StandardCharsets.UTF_8)));\n\n    try {\n      testDockerClient.inspect(ImageReference.of(null, \"image\", null));\n      Assert.fail(\"docker inspect should have failed\");\n\n    } catch (IOException ex) {\n      Assert.assertEquals(\"'docker inspect' command failed with error: error\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testSize_stdinFail() throws InterruptedException {\n    DockerClient testDockerClient =\n        new CliDockerClient(\n            subcommand -> {\n              Assert.assertEquals(\"inspect\", subcommand.get(0));\n              return mockProcessBuilder;\n            });\n    Mockito.when(mockProcess.getInputStream())\n        .thenReturn(\n            new InputStream() {\n\n              @Override\n              public int read() throws IOException {\n                throw new IOException();\n              }\n            });\n\n    assertThrows(\n        IOException.class, () -> testDockerClient.inspect(ImageReference.of(null, \"image\", null)));\n\n    Mockito.verify(mockProcess, Mockito.never()).waitFor();\n  }\n\n  @Test\n  public void testDockerImageDetails() throws DigestException, IOException {\n    String json =\n        \"{\\\"Size\\\":488118507,\"\n            + \"\\\"Id\\\":\\\"sha256:e8d00769c8a805a0656dbfd49d4f91cbc2e36d0199f10343d1beba36ecdcb3fd\\\",\"\n            + \"\\\"RootFS\\\": { \\\"Layers\\\" : [\"\n            + \"  \\\"sha256:55e6b89812f369277290d098c1e44c9e85a5ab0286c649f37e66e11074f8ebd1\\\",\"\n            + \"  \\\"sha256:26b1991f37bd5b798e1523f65d7f6aa6961b75515f465cf44123fa0ad3b8961b\\\",\"\n            + \"  \\\"sha256:8bacec4e34468110538ebf108ca8ec0d880a37018a55be91b9670b8e900c593a\\\"]}}\\n\";\n    DockerImageDetails results = JsonTemplateMapper.readJson(json, DockerImageDetails.class);\n    Assert.assertEquals(488118507, results.getSize());\n    Assert.assertEquals(\n        DescriptorDigest.fromHash(\n            \"e8d00769c8a805a0656dbfd49d4f91cbc2e36d0199f10343d1beba36ecdcb3fd\"),\n        results.getImageId());\n    Assert.assertEquals(\n        Arrays.asList(\n            DescriptorDigest.fromHash(\n                \"55e6b89812f369277290d098c1e44c9e85a5ab0286c649f37e66e11074f8ebd1\"),\n            DescriptorDigest.fromHash(\n                \"26b1991f37bd5b798e1523f65d7f6aa6961b75515f465cf44123fa0ad3b8961b\"),\n            DescriptorDigest.fromHash(\n                \"8bacec4e34468110538ebf108ca8ec0d880a37018a55be91b9670b8e900c593a\")),\n        results.getDiffIds());\n  }\n\n  @Test\n  public void testDockerImageDetails_unknownProperties() throws IOException, DigestException {\n    String json =\n        \"{\\\"Unknown\\\": [ ], \\\"Structure\\\": [ { \\\"Test\\\": 0 } ], \\\"Size\\\": 1234,\"\n            + \"\\\"Id\\\": \\\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\\",\"\n            + \"\\\"RootFS\\\": { \\\"Someting\\\": \\\"unrelated\\\", \\\"Layers\\\": [\"\n            + \"  \\\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\\\" ] } }\";\n    DockerImageDetails results = JsonTemplateMapper.readJson(json, DockerImageDetails.class);\n    Assert.assertEquals(1234, results.getSize());\n    Assert.assertEquals(\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"),\n        results.getImageId());\n    Assert.assertEquals(\n        Arrays.asList(\n            DescriptorDigest.fromHash(\n                \"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\")),\n        results.getDiffIds());\n  }\n\n  @Test\n  public void testDockerImageDetails_emptyJson() throws IOException, DigestException {\n    DockerImageDetails details = JsonTemplateMapper.readJson(\"{}\", DockerImageDetails.class);\n    Assert.assertEquals(0, details.getSize());\n    Assert.assertEquals(Collections.emptyList(), details.getDiffIds());\n    try {\n      details.getImageId();\n      Assert.fail();\n    } catch (DigestException ex) {\n      Assert.assertEquals(\"Invalid digest: \", ex.getMessage());\n    }\n  }\n\n  private DockerClient makeDockerSaveClient() {\n    return new CliDockerClient(\n        subcommand -> {\n          try {\n            if (subcommand.contains(\"{{.Size}}\")) {\n              // It doesn't matter what size is actually returned by 'docker inspect' here, so just\n              // use 150000 as a placeholder.\n              Process mockSizeProcess = Mockito.mock(Process.class);\n              Mockito.when(mockSizeProcess.getInputStream())\n                  .thenReturn(new ByteArrayInputStream(\"150000\".getBytes(StandardCharsets.UTF_8)));\n              Mockito.when(mockProcessBuilder.start()).thenReturn(mockSizeProcess);\n            } else {\n              Assert.assertEquals(Arrays.asList(\"save\", \"testimage\"), subcommand);\n              Mockito.when(mockProcess.getInputStream())\n                  .thenReturn(new ByteArrayInputStream(\"jib\".getBytes(StandardCharsets.UTF_8)));\n              Mockito.when(mockProcessBuilder.start()).thenReturn(mockProcess);\n            }\n          } catch (IOException ignored) {\n            // ignored\n          }\n          return mockProcessBuilder;\n        });\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplateTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.docker.json;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link DockerManifestEntryTemplate}. */\npublic class DockerManifestEntryTemplateTest {\n\n  @Test\n  public void testToJson() throws URISyntaxException, IOException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/loadmanifest.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    DockerManifestEntryTemplate template = new DockerManifestEntryTemplate();\n    template.addRepoTag(\n        ImageReference.of(\"testregistry\", \"testrepo\", \"testtag\").toStringWithQualifier());\n    template.addLayerFile(\"layer1.tar.gz\");\n    template.addLayerFile(\"layer2.tar.gz\");\n    template.addLayerFile(\"layer3.tar.gz\");\n\n    List<DockerManifestEntryTemplate> loadManifest = Collections.singletonList(template);\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(loadManifest));\n  }\n\n  @Test\n  public void testFromJson() throws URISyntaxException, IOException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/loadmanifest2.json\").toURI());\n    String sourceJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n    DockerManifestEntryTemplate template =\n        new ObjectMapper().readValue(sourceJson, DockerManifestEntryTemplate[].class)[0];\n\n    Assert.assertEquals(\n        ImmutableList.of(\"layer1.tar.gz\", \"layer2.tar.gz\", \"layer3.tar.gz\"),\n        template.getLayerFiles());\n    Assert.assertEquals(\"config.json\", template.getConfig());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/event/EventHandlersTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event;\n\nimport com.google.cloud.tools.jib.api.JibEvent;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\n/** Tests for {@link EventHandlers}. */\npublic class EventHandlersTest {\n\n  /** Test {@link JibEvent}. */\n  private interface TestJibEvent1 extends JibEvent {\n\n    String getPayload();\n  }\n\n  /** Test implementation of {@link JibEvent}. */\n  private static class TestJibEvent2 implements JibEvent {\n\n    @Nullable private String message;\n\n    private void assertMessageCorrect(String name) {\n      Assert.assertEquals(\"Hello \" + name, message);\n    }\n\n    private void sayHello(String name) {\n      Assert.assertNull(message);\n      message = \"Hello \" + name;\n    }\n  }\n\n  /** Test {@link JibEvent}. */\n  private static class TestJibEvent3 implements JibEvent {}\n\n  @Test\n  public void testAdd() {\n    int[] counter = new int[1];\n    EventHandlers eventHandlers =\n        EventHandlers.builder()\n            .add(\n                TestJibEvent1.class,\n                testJibEvent1 -> Assert.assertEquals(\"payload\", testJibEvent1.getPayload()))\n            .add(TestJibEvent2.class, testJibEvent2 -> testJibEvent2.sayHello(\"Jib\"))\n            .add(JibEvent.class, jibEvent -> counter[0]++)\n            .build();\n    Assert.assertTrue(eventHandlers.getHandlers().containsKey(JibEvent.class));\n    Assert.assertTrue(eventHandlers.getHandlers().containsKey(TestJibEvent1.class));\n    Assert.assertTrue(eventHandlers.getHandlers().containsKey(TestJibEvent2.class));\n    Assert.assertEquals(1, eventHandlers.getHandlers().get(JibEvent.class).size());\n    Assert.assertEquals(1, eventHandlers.getHandlers().get(TestJibEvent1.class).size());\n    Assert.assertEquals(1, eventHandlers.getHandlers().get(TestJibEvent2.class).size());\n\n    TestJibEvent1 mockTestJibEvent1 = Mockito.mock(TestJibEvent1.class);\n    Mockito.when(mockTestJibEvent1.getPayload()).thenReturn(\"payload\");\n    TestJibEvent2 testJibEvent2 = new TestJibEvent2();\n\n    // Checks that the handlers handled their respective event types.\n    eventHandlers.getHandlers().get(JibEvent.class).asList().get(0).handle(mockTestJibEvent1);\n    eventHandlers.getHandlers().get(JibEvent.class).asList().get(0).handle(testJibEvent2);\n    eventHandlers.getHandlers().get(TestJibEvent1.class).asList().get(0).handle(mockTestJibEvent1);\n    eventHandlers.getHandlers().get(TestJibEvent2.class).asList().get(0).handle(testJibEvent2);\n\n    Assert.assertEquals(2, counter[0]);\n    Mockito.verify(mockTestJibEvent1).getPayload();\n    Mockito.verifyNoMoreInteractions(mockTestJibEvent1);\n    testJibEvent2.assertMessageCorrect(\"Jib\");\n  }\n\n  @Test\n  public void testDispatch() {\n    List<String> emissions = new ArrayList<>();\n\n    EventHandlers eventHandlers =\n        EventHandlers.builder()\n            .add(TestJibEvent2.class, testJibEvent2 -> emissions.add(\"handled 2 first\"))\n            .add(TestJibEvent2.class, testJibEvent2 -> emissions.add(\"handled 2 second\"))\n            .add(TestJibEvent3.class, testJibEvent3 -> emissions.add(\"handled 3\"))\n            .add(JibEvent.class, jibEvent -> emissions.add(\"handled generic\"))\n            .build();\n\n    TestJibEvent2 testJibEvent2 = new TestJibEvent2();\n    TestJibEvent3 testJibEvent3 = new TestJibEvent3();\n\n    eventHandlers.dispatch(testJibEvent2);\n    eventHandlers.dispatch(testJibEvent3);\n\n    Assert.assertEquals(\n        Arrays.asList(\n            \"handled generic\",\n            \"handled 2 first\",\n            \"handled 2 second\",\n            \"handled generic\",\n            \"handled 3\"),\n        emissions);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/event/events/LogEventTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.events;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link LogEvent}. */\npublic class LogEventTest {\n\n  private final Deque<LogEvent> receivedLogEvents = new ArrayDeque<>();\n\n  // Note that in actual code, the event handler should NOT perform thread unsafe operations like\n  // here.\n  private final EventHandlers eventHandlers =\n      EventHandlers.builder().add(LogEvent.class, receivedLogEvents::offer).build();\n\n  @Test\n  public void testFactories() {\n    eventHandlers.dispatch(LogEvent.error(\"error\"));\n    eventHandlers.dispatch(LogEvent.lifecycle(\"lifecycle\"));\n    eventHandlers.dispatch(LogEvent.progress(\"progress\"));\n    eventHandlers.dispatch(LogEvent.warn(\"warn\"));\n    eventHandlers.dispatch(LogEvent.info(\"info\"));\n    eventHandlers.dispatch(LogEvent.debug(\"debug\"));\n\n    verifyNextLogEvent(Level.ERROR, \"error\");\n    verifyNextLogEvent(Level.LIFECYCLE, \"lifecycle\");\n    verifyNextLogEvent(Level.PROGRESS, \"progress\");\n    verifyNextLogEvent(Level.WARN, \"warn\");\n    verifyNextLogEvent(Level.INFO, \"info\");\n    verifyNextLogEvent(Level.DEBUG, \"debug\");\n    Assert.assertTrue(receivedLogEvents.isEmpty());\n  }\n\n  private void verifyNextLogEvent(Level level, String message) {\n    Assert.assertFalse(receivedLogEvents.isEmpty());\n\n    LogEvent logEvent = receivedLogEvents.poll();\n\n    Assert.assertEquals(level, logEvent.getLevel());\n    Assert.assertEquals(message, logEvent.getMessage());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/event/events/ProgressEventTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.events;\n\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.progress.Allocation;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ProgressEvent}. */\npublic class ProgressEventTest {\n\n  /** {@link Allocation} tree for testing. */\n  private static class AllocationTree {\n\n    /** The root node. */\n    private static final Allocation root = Allocation.newRoot(\"ignored\", 2);\n\n    /** First child of the root node. */\n    private static final Allocation child1 = root.newChild(\"ignored\", 1);\n    /** Child of the first child of the root node. */\n    private static final Allocation child1Child = child1.newChild(\"ignored\", 100);\n\n    /** Second child of the root node. */\n    private static final Allocation child2 = root.newChild(\"ignored\", 200);\n\n    private AllocationTree() {}\n  }\n\n  private static EventHandlers makeEventHandlers(Consumer<ProgressEvent> progressEventConsumer) {\n    return EventHandlers.builder().add(ProgressEvent.class, progressEventConsumer).build();\n  }\n\n  private static final double DOUBLE_ERROR_MARGIN = 1e-10;\n\n  private final Map<Allocation, Long> allocationCompletionMap = new HashMap<>();\n\n  private double progress = 0.0;\n\n  @Test\n  public void testAccumulateProgress() {\n    Consumer<ProgressEvent> progressEventConsumer =\n        progressEvent -> {\n          double fractionOfRoot = progressEvent.getAllocation().getFractionOfRoot();\n          long units = progressEvent.getUnits();\n\n          progress += units * fractionOfRoot;\n        };\n\n    EventHandlers eventHandlers = makeEventHandlers(progressEventConsumer);\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1Child, 50));\n    Assert.assertEquals(1.0 / 2 / 100 * 50, progress, DOUBLE_ERROR_MARGIN);\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1Child, 50));\n    Assert.assertEquals(1.0 / 2, progress, DOUBLE_ERROR_MARGIN);\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child2, 10));\n    Assert.assertEquals(1.0 / 2 + 1.0 / 2 / 200 * 10, progress, DOUBLE_ERROR_MARGIN);\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child2, 190));\n    Assert.assertEquals(1.0, progress, DOUBLE_ERROR_MARGIN);\n  }\n\n  @Test\n  public void testSmoke() {\n    Consumer<ProgressEvent> progressEventConsumer =\n        progressEvent -> {\n          Allocation allocation = progressEvent.getAllocation();\n          long units = progressEvent.getUnits();\n\n          updateCompletionMap(allocation, units);\n        };\n\n    EventHandlers eventHandlers = makeEventHandlers(progressEventConsumer);\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1Child, 50));\n\n    Assert.assertEquals(1, allocationCompletionMap.size());\n    Assert.assertEquals(50, allocationCompletionMap.get(AllocationTree.child1Child).longValue());\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1Child, 50));\n\n    Assert.assertEquals(3, allocationCompletionMap.size());\n    Assert.assertEquals(100, allocationCompletionMap.get(AllocationTree.child1Child).longValue());\n    Assert.assertEquals(1, allocationCompletionMap.get(AllocationTree.child1).longValue());\n    Assert.assertEquals(1, allocationCompletionMap.get(AllocationTree.root).longValue());\n\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child2, 200));\n\n    Assert.assertEquals(4, allocationCompletionMap.size());\n    Assert.assertEquals(100, allocationCompletionMap.get(AllocationTree.child1Child).longValue());\n    Assert.assertEquals(1, allocationCompletionMap.get(AllocationTree.child1).longValue());\n    Assert.assertEquals(200, allocationCompletionMap.get(AllocationTree.child2).longValue());\n    Assert.assertEquals(2, allocationCompletionMap.get(AllocationTree.root).longValue());\n  }\n\n  @Test\n  public void testType() {\n    // Used to test whether or not progress event was consumed\n    boolean[] called = new boolean[] {false};\n    Consumer<ProgressEvent> buildImageConsumer =\n        progressEvent -> {\n          called[0] = true;\n        };\n\n    EventHandlers eventHandlers = makeEventHandlers(buildImageConsumer);\n    eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1, 50));\n    Assert.assertTrue(called[0]);\n  }\n\n  /**\n   * Updates the {@link #allocationCompletionMap} with {@code units} more progress for {@code\n   * allocation}. This also updates {@link Allocation} parents if {@code allocation} is complete in\n   * terms of progress.\n   *\n   * @param allocation the allocation the progress is made on\n   * @param units the progress units\n   */\n  private void updateCompletionMap(Allocation allocation, long units) {\n    if (allocationCompletionMap.containsKey(allocation)) {\n      units += allocationCompletionMap.get(allocation);\n    }\n    allocationCompletionMap.put(allocation, units);\n\n    if (allocation.getAllocationUnits() == units) {\n      allocation\n          .getParent()\n          .ifPresent(parentAllocation -> updateCompletionMap(parentAllocation, 1));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/event/progress/AllocationCompletionTrackerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport com.google.cloud.tools.jib.MultithreadedExecutor;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link AllocationCompletionTracker}. */\npublic class AllocationCompletionTrackerTest {\n\n  /** {@link Allocation} tree for testing. */\n  private static class AllocationTree {\n\n    /** The root node. */\n    private static final Allocation root = Allocation.newRoot(\"root\", 2);\n\n    /** First child of the root node. */\n    private static final Allocation child1 = root.newChild(\"child1\", 1);\n    /** Child of the first child of the root node. */\n    private static final Allocation child1Child = child1.newChild(\"child1Child\", 100);\n\n    /** Second child of the root node. */\n    private static final Allocation child2 = root.newChild(\"child2\", 200);\n\n    private AllocationTree() {}\n  }\n\n  @Test\n  public void testGetUnfinishedAllocations_singleThread() {\n    AllocationCompletionTracker allocationCompletionTracker = new AllocationCompletionTracker();\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.root, 0L));\n    Assert.assertEquals(\n        Collections.singletonList(AllocationTree.root),\n        allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.child1, 0L));\n    Assert.assertEquals(\n        Arrays.asList(AllocationTree.root, AllocationTree.child1),\n        allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.child1Child, 0L));\n    Assert.assertEquals(\n        Arrays.asList(AllocationTree.root, AllocationTree.child1, AllocationTree.child1Child),\n        allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.child1Child, 50L));\n    Assert.assertEquals(\n        Arrays.asList(AllocationTree.root, AllocationTree.child1, AllocationTree.child1Child),\n        allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.child1Child, 50L));\n    Assert.assertEquals(\n        Collections.singletonList(AllocationTree.root),\n        allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.child2, 100L));\n    Assert.assertEquals(\n        Arrays.asList(AllocationTree.root, AllocationTree.child2),\n        allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertTrue(allocationCompletionTracker.updateProgress(AllocationTree.child2, 100L));\n    Assert.assertEquals(\n        Collections.emptyList(), allocationCompletionTracker.getUnfinishedAllocations());\n\n    Assert.assertFalse(allocationCompletionTracker.updateProgress(AllocationTree.child2, 0L));\n    Assert.assertEquals(\n        Collections.emptyList(), allocationCompletionTracker.getUnfinishedAllocations());\n\n    try {\n      allocationCompletionTracker.updateProgress(AllocationTree.child1, 1L);\n      Assert.fail();\n\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\"Progress exceeds max for 'child1': 1 more beyond 1\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testGetUnfinishedAllocations_multipleThreads()\n      throws InterruptedException, ExecutionException, IOException {\n    try (MultithreadedExecutor multithreadedExecutor = new MultithreadedExecutor()) {\n      AllocationCompletionTracker allocationCompletionTracker = new AllocationCompletionTracker();\n\n      // Adds root, child1, and child1Child.\n      Assert.assertEquals(\n          true,\n          multithreadedExecutor.invoke(\n              () -> allocationCompletionTracker.updateProgress(AllocationTree.root, 0L)));\n      Assert.assertEquals(\n          true,\n          multithreadedExecutor.invoke(\n              () -> allocationCompletionTracker.updateProgress(AllocationTree.child1, 0L)));\n      Assert.assertEquals(\n          true,\n          multithreadedExecutor.invoke(\n              () -> allocationCompletionTracker.updateProgress(AllocationTree.child1Child, 0L)));\n      Assert.assertEquals(\n          Arrays.asList(AllocationTree.root, AllocationTree.child1, AllocationTree.child1Child),\n          allocationCompletionTracker.getUnfinishedAllocations());\n\n      // Adds 50 to child1Child and 100 to child2.\n      List<Callable<Boolean>> callables = new ArrayList<>(150);\n      callables.addAll(\n          Collections.nCopies(\n              50,\n              () -> allocationCompletionTracker.updateProgress(AllocationTree.child1Child, 1L)));\n      callables.addAll(\n          Collections.nCopies(\n              100, () -> allocationCompletionTracker.updateProgress(AllocationTree.child2, 1L)));\n\n      Assert.assertEquals(\n          Collections.nCopies(150, true), multithreadedExecutor.invokeAll(callables));\n      Assert.assertEquals(\n          Arrays.asList(\n              AllocationTree.root,\n              AllocationTree.child1,\n              AllocationTree.child1Child,\n              AllocationTree.child2),\n          allocationCompletionTracker.getUnfinishedAllocations());\n\n      // 0 progress doesn't do anything.\n      Assert.assertEquals(\n          Collections.nCopies(100, false),\n          multithreadedExecutor.invokeAll(\n              Collections.nCopies(\n                  100,\n                  () -> allocationCompletionTracker.updateProgress(AllocationTree.child1, 0L))));\n      Assert.assertEquals(\n          Arrays.asList(\n              AllocationTree.root,\n              AllocationTree.child1,\n              AllocationTree.child1Child,\n              AllocationTree.child2),\n          allocationCompletionTracker.getUnfinishedAllocations());\n\n      // Adds 50 to child1Child and 100 to child2 to finish it up.\n      Assert.assertEquals(\n          Collections.nCopies(150, true), multithreadedExecutor.invokeAll(callables));\n      Assert.assertEquals(\n          Collections.emptyList(), allocationCompletionTracker.getUnfinishedAllocations());\n    }\n  }\n\n  @Test\n  public void testGetUnfinishedLeafTasks() {\n    AllocationCompletionTracker tracker = new AllocationCompletionTracker();\n    tracker.updateProgress(AllocationTree.root, 0);\n    Assert.assertEquals(Arrays.asList(\"root\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1, 0);\n    Assert.assertEquals(Arrays.asList(\"child1\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child2, 0);\n    Assert.assertEquals(Arrays.asList(\"child1\", \"child2\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1Child, 0);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1Child, 50);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child2, 100);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child2, 100);\n    Assert.assertEquals(Arrays.asList(\"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1Child, 50);\n    Assert.assertEquals(Collections.emptyList(), tracker.getUnfinishedLeafTasks());\n  }\n\n  @Test\n  public void testGetUnfinishedLeafTasks_differentUpdateOrder() {\n    AllocationCompletionTracker tracker = new AllocationCompletionTracker();\n    tracker.updateProgress(AllocationTree.root, 0);\n    Assert.assertEquals(Arrays.asList(\"root\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child2, 0);\n    Assert.assertEquals(Arrays.asList(\"child2\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1, 0);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1Child, 0);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1Child, 50);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child2, 100);\n    Assert.assertEquals(Arrays.asList(\"child2\", \"child1Child\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child1Child, 50);\n    Assert.assertEquals(Arrays.asList(\"child2\"), tracker.getUnfinishedLeafTasks());\n\n    tracker.updateProgress(AllocationTree.child2, 100);\n    Assert.assertEquals(Collections.emptyList(), tracker.getUnfinishedLeafTasks());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/event/progress/AllocationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Allocation}. */\npublic class AllocationTest {\n\n  /** Error margin for checking equality of two doubles. */\n  private static final double DOUBLE_ERROR_MARGIN = 1e-10;\n\n  @Test\n  public void testSmoke_linear() {\n    Allocation root = Allocation.newRoot(\"root\", 1);\n    Allocation node1 = root.newChild(\"node1\", 2);\n    Allocation node2 = node1.newChild(\"node2\", 3);\n\n    Assert.assertEquals(\"node2\", node2.getDescription());\n    Assert.assertEquals(3, node2.getAllocationUnits());\n    Assert.assertEquals(1.0 / 2 / 3, node2.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertTrue(node2.getParent().isPresent());\n    Assert.assertEquals(node1, node2.getParent().get());\n\n    Assert.assertEquals(\"node1\", node1.getDescription());\n    Assert.assertEquals(2, node1.getAllocationUnits());\n    Assert.assertTrue(node1.getParent().isPresent());\n    Assert.assertEquals(root, node1.getParent().get());\n    Assert.assertEquals(1.0 / 2, node1.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n\n    Assert.assertEquals(\"root\", root.getDescription());\n    Assert.assertEquals(1, root.getAllocationUnits());\n    Assert.assertFalse(root.getParent().isPresent());\n    Assert.assertEquals(1.0, root.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n  }\n\n  @Test\n  public void testFractionOfRoot_tree_partial() {\n    Allocation root = Allocation.newRoot(\"ignored\", 10);\n    Allocation left = root.newChild(\"ignored\", 2);\n    Allocation right = root.newChild(\"ignored\", 4);\n    Allocation leftDown = left.newChild(\"ignored\", 20);\n    Allocation rightLeft = right.newChild(\"ignored\", 20);\n    Allocation rightRight = right.newChild(\"ignored\", 100);\n    Allocation rightRightDown = rightRight.newChild(\"ignored\", 200);\n\n    Assert.assertEquals(1.0 / 10, root.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1.0 / 10 / 2, left.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1.0 / 10 / 4, right.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1.0 / 10 / 2 / 20, leftDown.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1.0 / 10 / 4 / 20, rightLeft.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1.0 / 10 / 4 / 100, rightRight.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(\n        1.0 / 10 / 4 / 100 / 200, rightRightDown.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n  }\n\n  @Test\n  public void testFractionOfRoot_tree_complete() {\n    Allocation root = Allocation.newRoot(\"ignored\", 2);\n\n    Allocation left = root.newChild(\"ignored\", 3);\n    Allocation leftLeft = left.newChild(\"ignored\", 1);\n    Allocation leftLeftDown = leftLeft.newChild(\"ignored\", 100);\n    Allocation leftMiddle = left.newChild(\"ignored\", 100);\n    Allocation leftRight = left.newChild(\"ignored\", 100);\n\n    Allocation right = root.newChild(\"ignored\", 1);\n    Allocation rightDown = right.newChild(\"ignored\", 100);\n\n    // Checks that the leaf allocations add up to a full 1.0.\n    double total =\n        leftLeftDown.getFractionOfRoot() * leftLeftDown.getAllocationUnits()\n            + leftMiddle.getFractionOfRoot() * leftMiddle.getAllocationUnits()\n            + leftRight.getFractionOfRoot() * leftRight.getAllocationUnits()\n            + rightDown.getFractionOfRoot() * rightDown.getAllocationUnits();\n    Assert.assertEquals(1.0, total, DOUBLE_ERROR_MARGIN);\n  }\n\n  @Test\n  public void testNonPositiveAllocationUnits() {\n    Allocation root = Allocation.newRoot(\"ignored\", 2);\n\n    Allocation left = root.newChild(\"ignored\", -30);\n    Allocation right = root.newChild(\"ignored\", 0);\n\n    Assert.assertEquals(0.5, root.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(2, root.getAllocationUnits());\n\n    Assert.assertEquals(0.5, left.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1, left.getAllocationUnits());\n\n    Assert.assertEquals(0.5, right.getFractionOfRoot(), DOUBLE_ERROR_MARGIN);\n    Assert.assertEquals(1, right.getAllocationUnits());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/event/progress/ProgressEventHandlerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.event.progress;\n\nimport com.google.cloud.tools.jib.MultithreadedExecutor;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.atomic.DoubleAccumulator;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ProgressEventHandler}. */\npublic class ProgressEventHandlerTest {\n\n  /** {@link Allocation} tree for testing. */\n  private static class AllocationTree {\n\n    /** The root node. */\n    private static final Allocation root = Allocation.newRoot(\"root\", 2);\n\n    /** First child of the root node. */\n    private static final Allocation child1 = root.newChild(\"child1\", 1);\n    /** Child of the first child of the root node. */\n    private static final Allocation child1Child = child1.newChild(\"child1Child\", 100);\n\n    /** Second child of the root node. */\n    private static final Allocation child2 = root.newChild(\"child2\", 200);\n\n    private AllocationTree() {}\n  }\n\n  private static final double DOUBLE_ERROR_MARGIN = 1e-10;\n\n  @Test\n  public void testAccept() throws ExecutionException, InterruptedException, IOException {\n    try (MultithreadedExecutor multithreadedExecutor = new MultithreadedExecutor()) {\n      DoubleAccumulator maxProgress = new DoubleAccumulator(Double::max, 0);\n\n      ProgressEventHandler progressEventHandler =\n          new ProgressEventHandler(update -> maxProgress.accumulate(update.getProgress()));\n      EventHandlers eventHandlers =\n          EventHandlers.builder().add(ProgressEvent.class, progressEventHandler).build();\n\n      // Adds root, child1, and child1Child.\n      multithreadedExecutor.invoke(\n          () -> {\n            eventHandlers.dispatch(new ProgressEvent(AllocationTree.root, 0L));\n            return null;\n          });\n      multithreadedExecutor.invoke(\n          () -> {\n            eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1, 0L));\n            return null;\n          });\n      multithreadedExecutor.invoke(\n          () -> {\n            eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1Child, 0L));\n            return null;\n          });\n      Assert.assertEquals(0.0, maxProgress.get(), DOUBLE_ERROR_MARGIN);\n\n      // Adds 50 to child1Child and 100 to child2.\n      List<Callable<Void>> callables = new ArrayList<>(150);\n      callables.addAll(\n          Collections.nCopies(\n              50,\n              () -> {\n                eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1Child, 1L));\n                return null;\n              }));\n      callables.addAll(\n          Collections.nCopies(\n              100,\n              () -> {\n                eventHandlers.dispatch(new ProgressEvent(AllocationTree.child2, 1L));\n                return null;\n              }));\n\n      multithreadedExecutor.invokeAll(callables);\n\n      Assert.assertEquals(\n          1.0 / 2 / 100 * 50 + 1.0 / 2 / 200 * 100, maxProgress.get(), DOUBLE_ERROR_MARGIN);\n\n      // 0 progress doesn't do anything.\n      multithreadedExecutor.invokeAll(\n          Collections.nCopies(\n              100,\n              () -> {\n                eventHandlers.dispatch(new ProgressEvent(AllocationTree.child1, 0L));\n                return null;\n              }));\n      Assert.assertEquals(\n          1.0 / 2 / 100 * 50 + 1.0 / 2 / 200 * 100, maxProgress.get(), DOUBLE_ERROR_MARGIN);\n\n      // Adds 50 to child1Child and 100 to child2 to finish it up.\n      multithreadedExecutor.invokeAll(callables);\n      Assert.assertEquals(1.0, maxProgress.get(), DOUBLE_ERROR_MARGIN);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/filesystem/DirectoryWalkerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/** Tests for {@link DirectoryWalker}. */\npublic class DirectoryWalkerTest {\n\n  private final Set<Path> walkedPaths = new HashSet<>();\n  private final PathConsumer addToWalkedPaths = walkedPaths::add;\n\n  private Path testDir;\n\n  @Before\n  public void setUp() throws URISyntaxException {\n    testDir = Paths.get(Resources.getResource(\"core/layer\").toURI());\n  }\n\n  @Test\n  public void testWalk() throws IOException {\n    new DirectoryWalker(testDir).walk(addToWalkedPaths);\n\n    Set<Path> expectedPaths =\n        new HashSet<>(\n            Arrays.asList(\n                testDir,\n                testDir.resolve(\"a\"),\n                testDir.resolve(\"a\").resolve(\"b\"),\n                testDir.resolve(\"a\").resolve(\"b\").resolve(\"bar\"),\n                testDir.resolve(\"c\"),\n                testDir.resolve(\"c\").resolve(\"cat\"),\n                testDir.resolve(\"foo\")));\n    Assert.assertEquals(expectedPaths, walkedPaths);\n  }\n\n  @Test\n  public void testWalk_withFilter() throws IOException {\n    // Filters to immediate subdirectories of testDir, and foo.\n    new DirectoryWalker(testDir)\n        .filter(path -> path.getParent().equals(testDir))\n        .filter(path -> !path.endsWith(\"foo\"))\n        .walk(addToWalkedPaths);\n\n    Set<Path> expectedPaths =\n        new HashSet<>(Arrays.asList(testDir.resolve(\"a\"), testDir.resolve(\"c\")));\n    Assert.assertEquals(expectedPaths, walkedPaths);\n  }\n\n  @Test\n  public void testWalk_withFilterRoot() throws IOException {\n    new DirectoryWalker(testDir).filterRoot().walk(addToWalkedPaths);\n\n    Set<Path> expectedPaths =\n        new HashSet<>(\n            Arrays.asList(\n                testDir.resolve(\"a\"),\n                testDir.resolve(\"a\").resolve(\"b\"),\n                testDir.resolve(\"a\").resolve(\"b\").resolve(\"bar\"),\n                testDir.resolve(\"c\"),\n                testDir.resolve(\"c\").resolve(\"cat\"),\n                testDir.resolve(\"foo\")));\n    Assert.assertEquals(expectedPaths, walkedPaths);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/filesystem/FileOperationsTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link FileOperations}. */\npublic class FileOperationsTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testCopy() throws IOException, URISyntaxException {\n    Path destDir = temporaryFolder.newFolder().toPath();\n    Path libraryA =\n        Paths.get(Resources.getResource(\"core/application/dependencies/libraryA.jar\").toURI());\n    Path libraryB =\n        Paths.get(Resources.getResource(\"core/application/dependencies/libraryB.jar\").toURI());\n    Path dirLayer = Paths.get(Resources.getResource(\"core/layer\").toURI());\n\n    FileOperations.copy(ImmutableList.of(libraryA, libraryB, dirLayer), destDir);\n\n    assertFilesEqual(libraryA, destDir.resolve(\"libraryA.jar\"));\n    assertFilesEqual(libraryB, destDir.resolve(\"libraryB.jar\"));\n    Assert.assertTrue(Files.exists(destDir.resolve(\"layer\").resolve(\"a\").resolve(\"b\")));\n    Assert.assertTrue(Files.exists(destDir.resolve(\"layer\").resolve(\"c\")));\n    assertFilesEqual(\n        dirLayer.resolve(\"a\").resolve(\"b\").resolve(\"bar\"),\n        destDir.resolve(\"layer\").resolve(\"a\").resolve(\"b\").resolve(\"bar\"));\n    assertFilesEqual(\n        dirLayer.resolve(\"c\").resolve(\"cat\"), destDir.resolve(\"layer\").resolve(\"c\").resolve(\"cat\"));\n    assertFilesEqual(dirLayer.resolve(\"foo\"), destDir.resolve(\"layer\").resolve(\"foo\"));\n  }\n\n  private void assertFilesEqual(Path file1, Path file2) throws IOException {\n    Assert.assertArrayEquals(Files.readAllBytes(file1), Files.readAllBytes(file2));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/filesystem/LockFileTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link LockFile}. */\npublic class LockFileTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testLockAndRelease() throws InterruptedException {\n    AtomicInteger atomicInt = new AtomicInteger(0);\n\n    // Runnable that would produce a race condition without a lock file\n    Runnable procedure =\n        () -> {\n          try (LockFile ignored =\n              LockFile.lock(temporaryFolder.getRoot().toPath().resolve(\"testLock\"))) {\n            Assert.assertTrue(Files.exists(temporaryFolder.getRoot().toPath().resolve(\"testLock\")));\n\n            int valueBeforeSleep = atomicInt.intValue();\n            Thread.sleep(100);\n            atomicInt.set(valueBeforeSleep + 1);\n\n          } catch (InterruptedException | IOException ex) {\n            throw new AssertionError(ex);\n          }\n        };\n\n    // Run the runnable once in this thread + once in the main thread\n    Thread thread = new Thread(procedure);\n    thread.start();\n    procedure.run();\n    thread.join();\n\n    // Assert no overlap while lock was in place\n    Assert.assertEquals(2, atomicInt.intValue());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/filesystem/TempDirectoryProviderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link TempDirectoryProvider}. */\npublic class TempDirectoryProviderTest {\n\n  private static void createFilesInDirectory(Path directory)\n      throws IOException, URISyntaxException {\n    Path testFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    new DirectoryWalker(testFilesDirectory)\n        .filterRoot()\n        .walk(path -> Files.copy(path, directory.resolve(testFilesDirectory.relativize(path))));\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testClose_directoriesDeleted() throws IOException, URISyntaxException {\n    Path parent = temporaryFolder.newFolder().toPath();\n\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      Path directory1 = tempDirectoryProvider.newDirectory(parent);\n      createFilesInDirectory(directory1);\n      Path directory2 = tempDirectoryProvider.newDirectory(parent);\n      createFilesInDirectory(directory2);\n\n      tempDirectoryProvider.close();\n      Assert.assertFalse(Files.exists(directory1));\n      Assert.assertFalse(Files.exists(directory2));\n    }\n  }\n\n  @Test\n  public void testClose_directoryNotDeletedIfMoved() throws IOException, URISyntaxException {\n    Path destinationParent = temporaryFolder.newFolder().toPath();\n\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      Path directory = tempDirectoryProvider.newDirectory(destinationParent);\n      createFilesInDirectory(directory);\n\n      Assert.assertFalse(Files.exists(destinationParent.resolve(\"destination\")));\n      Files.move(directory, destinationParent.resolve(\"destination\"));\n\n      tempDirectoryProvider.close();\n      Assert.assertFalse(Files.exists(directory));\n      Assert.assertTrue(Files.exists(destinationParent.resolve(\"destination\")));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/filesystem/XdgDirectoriesTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.filesystem;\n\nimport com.google.common.collect.ImmutableMap;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Properties;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link XdgDirectories}. */\npublic class XdgDirectoriesTest {\n\n  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private String fakeCacheHome;\n  private String fakeConfigHome;\n\n  @Before\n  public void setUp() throws IOException {\n    fakeCacheHome = temporaryFolder.newFolder().getPath();\n    fakeConfigHome = temporaryFolder.newFolder().getPath();\n  }\n\n  @Test\n  public void testGetCacheHome_hasXdgCacheHome() {\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", fakeCacheHome);\n    Map<String, String> fakeEnvironment = ImmutableMap.of(\"XDG_CACHE_HOME\", fakeCacheHome);\n\n    fakeProperties.setProperty(\"os.name\", \"linux\");\n    Assert.assertEquals(\n        Paths.get(fakeCacheHome).resolve(\"google-cloud-tools-java\").resolve(\"jib\"),\n        XdgDirectories.getCacheHome(fakeProperties, fakeEnvironment));\n\n    fakeProperties.setProperty(\"os.name\", \"windows\");\n    Assert.assertEquals(\n        Paths.get(fakeCacheHome).resolve(\"Google\").resolve(\"Jib\").resolve(\"Cache\"),\n        XdgDirectories.getCacheHome(fakeProperties, fakeEnvironment));\n\n    fakeProperties.setProperty(\"os.name\", \"mac\");\n    Assert.assertEquals(\n        Paths.get(fakeCacheHome).resolve(\"Google\").resolve(\"Jib\"),\n        XdgDirectories.getCacheHome(fakeProperties, fakeEnvironment));\n  }\n\n  @Test\n  public void testGetCacheHome_linux() {\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", fakeCacheHome);\n    fakeProperties.setProperty(\"os.name\", \"os is LiNuX\");\n\n    Assert.assertEquals(\n        Paths.get(fakeCacheHome, \".cache\").resolve(\"google-cloud-tools-java\").resolve(\"jib\"),\n        XdgDirectories.getCacheHome(fakeProperties, Collections.emptyMap()));\n  }\n\n  @Test\n  public void testGetCacheHome_windows() {\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", \"nonexistent\");\n    fakeProperties.setProperty(\"os.name\", \"os is WiNdOwS\");\n\n    Map<String, String> fakeEnvironment = ImmutableMap.of(\"LOCALAPPDATA\", fakeCacheHome);\n\n    Assert.assertEquals(\n        Paths.get(fakeCacheHome).resolve(\"Google\").resolve(\"Jib\").resolve(\"Cache\"),\n        XdgDirectories.getCacheHome(fakeProperties, fakeEnvironment));\n  }\n\n  @Test\n  public void testGetCacheHome_mac() throws IOException {\n    Path libraryApplicationSupport = Paths.get(fakeCacheHome, \"Library\", \"Caches\");\n    Files.createDirectories(libraryApplicationSupport);\n\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", fakeCacheHome);\n    fakeProperties.setProperty(\"os.name\", \"os is mAc or DaRwIn\");\n\n    Assert.assertEquals(\n        libraryApplicationSupport.resolve(\"Google\").resolve(\"Jib\"),\n        XdgDirectories.getCacheHome(fakeProperties, Collections.emptyMap()));\n  }\n\n  @Test\n  public void testGetConfigHome_hasXdgConfigHome() {\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", fakeConfigHome);\n    Map<String, String> fakeEnvironment = ImmutableMap.of(\"XDG_CONFIG_HOME\", fakeConfigHome);\n\n    fakeProperties.setProperty(\"os.name\", \"linux\");\n    Assert.assertEquals(\n        Paths.get(fakeConfigHome).resolve(\"google-cloud-tools-java\").resolve(\"jib\"),\n        XdgDirectories.getConfigHome(fakeProperties, fakeEnvironment));\n\n    fakeProperties.setProperty(\"os.name\", \"windows\");\n    Assert.assertEquals(\n        Paths.get(fakeConfigHome).resolve(\"Google\").resolve(\"Jib\").resolve(\"Config\"),\n        XdgDirectories.getConfigHome(fakeProperties, fakeEnvironment));\n\n    fakeProperties.setProperty(\"os.name\", \"mac\");\n    Assert.assertEquals(\n        Paths.get(fakeConfigHome).resolve(\"Google\").resolve(\"Jib\"),\n        XdgDirectories.getConfigHome(fakeProperties, fakeEnvironment));\n  }\n\n  @Test\n  public void testGetConfigHome_linux() {\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", fakeConfigHome);\n    fakeProperties.setProperty(\"os.name\", \"os is LiNuX\");\n\n    Assert.assertEquals(\n        Paths.get(fakeConfigHome, \".config\").resolve(\"google-cloud-tools-java\").resolve(\"jib\"),\n        XdgDirectories.getConfigHome(fakeProperties, Collections.emptyMap()));\n  }\n\n  @Test\n  public void testGetConfigHome_windows() {\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", \"nonexistent\");\n    fakeProperties.setProperty(\"os.name\", \"os is WiNdOwS\");\n\n    Map<String, String> fakeEnvironment = ImmutableMap.of(\"LOCALAPPDATA\", fakeConfigHome);\n\n    Assert.assertEquals(\n        Paths.get(fakeConfigHome).resolve(\"Google\").resolve(\"Jib\").resolve(\"Config\"),\n        XdgDirectories.getConfigHome(fakeProperties, fakeEnvironment));\n  }\n\n  @Test\n  public void testGetConfigHome_mac() throws IOException {\n    Path libraryApplicationSupport = Paths.get(fakeConfigHome, \"Library\", \"Preferences\");\n    Files.createDirectories(libraryApplicationSupport);\n\n    Properties fakeProperties = new Properties();\n    fakeProperties.setProperty(\"user.home\", fakeConfigHome);\n    fakeProperties.setProperty(\"os.name\", \"os is mAc or DaRwIn\");\n\n    Assert.assertEquals(\n        libraryApplicationSupport.resolve(\"Google\").resolve(\"Jib\"),\n        XdgDirectories.getConfigHome(fakeProperties, Collections.emptyMap()));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactoryTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.frontend;\n\nimport com.google.auth.oauth2.AccessToken;\nimport com.google.auth.oauth2.GoogleCredentials;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory.DockerCredentialHelperFactory;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialHelperNotFoundException;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialHelperUnhandledServerUrlException;\nimport com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;\nimport com.google.cloud.tools.jib.registry.credentials.DockerConfigCredentialRetriever;\nimport com.google.cloud.tools.jib.registry.credentials.DockerCredentialHelper;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link CredentialRetrieverFactory}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class CredentialRetrieverFactoryTest {\n\n  private static final Credential FAKE_CREDENTIALS = Credential.from(\"username\", \"password\");\n\n  @Mock private Consumer<LogEvent> mockLogger;\n  @Mock private DockerCredentialHelper mockDockerCredentialHelper;\n  @Mock private DockerCredentialHelperFactory mockDockerCredentialHelperFactory;\n  @Mock private GoogleCredentials mockGoogleCredentials;\n\n  @Before\n  public void setUp()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    Mockito.when(\n            mockDockerCredentialHelperFactory.create(\n                Mockito.anyString(), Mockito.any(Path.class), Mockito.anyMap()))\n        .thenReturn(mockDockerCredentialHelper);\n    Mockito.when(mockDockerCredentialHelper.retrieve()).thenReturn(FAKE_CREDENTIALS);\n    Mockito.when(mockGoogleCredentials.getAccessToken())\n        .thenReturn(new AccessToken(\"my-token\", null));\n  }\n\n  @Test\n  public void testDockerCredentialHelper() throws CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"registry\", \"repo\");\n\n    Assert.assertEquals(\n        Optional.of(FAKE_CREDENTIALS),\n        credentialRetrieverFactory\n            .dockerCredentialHelper(Paths.get(\"docker-credential-foo\"))\n            .retrieve());\n\n    Mockito.verify(mockDockerCredentialHelperFactory)\n        .create(\"registry\", Paths.get(\"docker-credential-foo\"), Collections.emptyMap());\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.lifecycle(\"Using credential helper docker-credential-foo for registry/repo\"));\n  }\n\n  @Test\n  public void testDockerCredentialHelperWithEnvironment() throws CredentialRetrievalException {\n    Map<String, String> environment = Collections.singletonMap(\"ENV_VARIABLE\", \"Value\");\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"registry\", \"repo\", environment);\n\n    Assert.assertEquals(\n        Optional.of(FAKE_CREDENTIALS),\n        credentialRetrieverFactory\n            .dockerCredentialHelper(Paths.get(\"docker-credential-foo\"))\n            .retrieve());\n\n    Mockito.verify(mockDockerCredentialHelperFactory)\n        .create(\"registry\", Paths.get(\"docker-credential-foo\"), environment);\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.lifecycle(\"Using credential helper docker-credential-foo for registry/repo\"));\n  }\n\n  @Test\n  public void testWellKnownCredentialHelpers() throws CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"something.gcr.io\", \"repo\");\n\n    Assert.assertEquals(\n        Optional.of(FAKE_CREDENTIALS),\n        credentialRetrieverFactory.wellKnownCredentialHelpers().retrieve());\n\n    Mockito.verify(mockDockerCredentialHelperFactory)\n        .create(\"something.gcr.io\", Paths.get(\"docker-credential-gcr\"), Collections.emptyMap());\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.lifecycle(\n                \"Using credential helper docker-credential-gcr for something.gcr.io/repo\"));\n  }\n\n  @Test\n  public void testWellKnownCredentialHelpers_info()\n      throws CredentialRetrievalException, IOException {\n    CredentialHelperNotFoundException notFoundException =\n        Mockito.mock(CredentialHelperNotFoundException.class);\n    Mockito.when(notFoundException.getMessage()).thenReturn(\"warning\");\n    Mockito.when(notFoundException.getCause()).thenReturn(new IOException(\"the root cause\"));\n    Mockito.when(mockDockerCredentialHelper.retrieve()).thenThrow(notFoundException);\n\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"something.amazonaws.com\", \"repo\");\n\n    Assert.assertFalse(\n        credentialRetrieverFactory.wellKnownCredentialHelpers().retrieve().isPresent());\n\n    Mockito.verify(mockDockerCredentialHelperFactory)\n        .create(\n            \"something.amazonaws.com\",\n            Paths.get(\"docker-credential-ecr-login\"),\n            Collections.emptyMap());\n    Mockito.verify(mockLogger).accept(LogEvent.info(\"warning\"));\n    Mockito.verify(mockLogger).accept(LogEvent.info(\"  Caused by: the root cause\"));\n  }\n\n  @Test\n  public void testDockerConfig() throws IOException, CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"registry\", \"repo\");\n\n    Path dockerConfig = Paths.get(\"/foo/config.json\");\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        Mockito.mock(DockerConfigCredentialRetriever.class);\n    Mockito.when(dockerConfigCredentialRetriever.retrieve(mockLogger))\n        .thenReturn(Optional.of(FAKE_CREDENTIALS));\n    Mockito.when(dockerConfigCredentialRetriever.getDockerConfigFile()).thenReturn(dockerConfig);\n\n    Assert.assertEquals(\n        Optional.of(FAKE_CREDENTIALS),\n        credentialRetrieverFactory.dockerConfig(dockerConfigCredentialRetriever).retrieve());\n\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.lifecycle(\n                \"Using credentials from Docker config (\" + dockerConfig + \") for registry/repo\"));\n  }\n\n  @Test\n  public void testGoogleApplicationDefaultCredentials_notGoogleContainerRegistry()\n      throws CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"non.gcr.registry\", \"repository\");\n\n    Assert.assertFalse(\n        credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().isPresent());\n\n    Mockito.verifyNoInteractions(mockLogger);\n  }\n\n  @Test\n  public void testGoogleApplicationDefaultCredentials_adcNotPresent()\n      throws CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        new CredentialRetrieverFactory(\n            ImageReference.of(\"awesome.gcr.io\", \"repository\", null),\n            mockLogger,\n            mockDockerCredentialHelperFactory,\n            () -> {\n              throw new IOException(\"ADC not present\");\n            },\n            Collections.emptyMap());\n\n    Assert.assertFalse(\n        credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().isPresent());\n\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.info(\"ADC not present or error fetching access token: ADC not present\"));\n  }\n\n  @Test\n  public void testGoogleApplicationDefaultCredentials_refreshFailure()\n      throws CredentialRetrievalException, IOException {\n    Mockito.doThrow(new IOException(\"refresh failed\"))\n        .when(mockGoogleCredentials)\n        .refreshIfExpired();\n\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"awesome.gcr.io\", \"repository\");\n\n    Assert.assertFalse(\n        credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().isPresent());\n\n    Mockito.verify(mockLogger).accept(LogEvent.info(\"Google ADC found\"));\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.info(\"ADC not present or error fetching access token: refresh failed\"));\n    Mockito.verifyNoMoreInteractions(mockLogger);\n  }\n\n  @Test\n  public void testGoogleApplicationDefaultCredentials_endUserCredentials()\n      throws CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"awesome.gcr.io\", \"repo\");\n\n    Credential credential =\n        credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().get();\n    Assert.assertEquals(\"oauth2accesstoken\", credential.getUsername());\n    Assert.assertEquals(\"my-token\", credential.getPassword());\n\n    Mockito.verify(mockGoogleCredentials, Mockito.never()).createScoped(Mockito.anyString());\n\n    Mockito.verify(mockLogger).accept(LogEvent.info(\"Google ADC found\"));\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.lifecycle(\n                \"Using Google Application Default Credentials for awesome.gcr.io/repo\"));\n    Mockito.verifyNoMoreInteractions(mockLogger);\n  }\n\n  @Test\n  public void testGoogleApplicationDefaultCredentials_endUserCredentials_artifactRegistry()\n      throws CredentialRetrievalException {\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"us-docker.pkg.dev\", \"my-project/repo/my-app\");\n\n    Credential credential =\n        credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().get();\n    Assert.assertEquals(\"oauth2accesstoken\", credential.getUsername());\n    Assert.assertEquals(\"my-token\", credential.getPassword());\n\n    Mockito.verify(mockGoogleCredentials, Mockito.never()).createScoped(Mockito.anyString());\n\n    Mockito.verify(mockLogger).accept(LogEvent.info(\"Google ADC found\"));\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.lifecycle(\n                \"Using Google Application Default Credentials for \"\n                    + \"us-docker.pkg.dev/my-project/repo/my-app\"));\n    Mockito.verifyNoMoreInteractions(mockLogger);\n  }\n\n  @Test\n  public void testGoogleApplicationDefaultCredentials_serviceAccount()\n      throws CredentialRetrievalException {\n    Mockito.when(mockGoogleCredentials.createScopedRequired()).thenReturn(true);\n    Mockito.when(mockGoogleCredentials.createScoped(Mockito.anyCollection()))\n        .thenReturn(mockGoogleCredentials);\n\n    CredentialRetrieverFactory credentialRetrieverFactory =\n        createCredentialRetrieverFactory(\"gcr.io\", \"repo\");\n\n    Credential credential =\n        credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().get();\n    Assert.assertEquals(\"oauth2accesstoken\", credential.getUsername());\n    Assert.assertEquals(\"my-token\", credential.getPassword());\n\n    Mockito.verify(mockGoogleCredentials)\n        .createScoped(\n            Collections.singletonList(\"https://www.googleapis.com/auth/devstorage.read_write\"));\n\n    Mockito.verify(mockLogger).accept(LogEvent.info(\"Google ADC found\"));\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.info(\"ADC is a service account. Setting GCS read-write scope\"));\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.lifecycle(\"Using Google Application Default Credentials for gcr.io/repo\"));\n    Mockito.verifyNoMoreInteractions(mockLogger);\n  }\n\n  private CredentialRetrieverFactory createCredentialRetrieverFactory(\n      String registry, String repository) {\n    return createCredentialRetrieverFactory(registry, repository, Collections.emptyMap());\n  }\n\n  private CredentialRetrieverFactory createCredentialRetrieverFactory(\n      String registry, String repository, Map<String, String> environment) {\n    ImageReference imageReference = ImageReference.of(registry, repository, null);\n    return new CredentialRetrieverFactory(\n        imageReference,\n        mockLogger,\n        mockDockerCredentialHelperFactory,\n        () -> mockGoogleCredentials,\n        environment);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/global/JibSystemPropertiesTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.global;\n\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\n\n/** Tests for {@link JibSystemProperties}. */\npublic class JibSystemPropertiesTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  @Test\n  public void testCheckHttpTimeoutProperty_okWhenUndefined() throws NumberFormatException {\n    System.clearProperty(JibSystemProperties.HTTP_TIMEOUT);\n    JibSystemProperties.checkHttpTimeoutProperty();\n  }\n\n  @Test\n  public void testCheckHttpTimeoutProperty_stringValue() {\n    System.setProperty(JibSystemProperties.HTTP_TIMEOUT, \"random string\");\n    try {\n      JibSystemProperties.checkHttpTimeoutProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"jib.httpTimeout must be an integer: random string\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckHttpProxyPortProperty_undefined() throws NumberFormatException {\n    System.clearProperty(\"http.proxyPort\");\n    System.clearProperty(\"https.proxyPort\");\n    JibSystemProperties.checkProxyPortProperty();\n  }\n\n  @Test\n  public void testCheckHttpProxyPortProperty() throws NumberFormatException {\n    System.setProperty(\"http.proxyPort\", \"0\");\n    System.setProperty(\"https.proxyPort\", \"0\");\n    JibSystemProperties.checkProxyPortProperty();\n\n    System.setProperty(\"http.proxyPort\", \"1\");\n    System.setProperty(\"https.proxyPort\", \"1\");\n    JibSystemProperties.checkProxyPortProperty();\n\n    System.setProperty(\"http.proxyPort\", \"65535\");\n    System.setProperty(\"https.proxyPort\", \"65535\");\n    JibSystemProperties.checkProxyPortProperty();\n\n    System.setProperty(\"http.proxyPort\", \"65534\");\n    System.setProperty(\"https.proxyPort\", \"65534\");\n    JibSystemProperties.checkProxyPortProperty();\n  }\n\n  @Test\n  public void testCheckHttpProxyPortProperty_negativeValue() {\n    System.setProperty(\"http.proxyPort\", \"-1\");\n    System.clearProperty(\"https.proxyPort\");\n    try {\n      JibSystemProperties.checkProxyPortProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"http.proxyPort cannot be less than 0: -1\", ex.getMessage());\n    }\n\n    System.clearProperty(\"http.proxyPort\");\n    System.setProperty(\"https.proxyPort\", \"-1\");\n    try {\n      JibSystemProperties.checkProxyPortProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"https.proxyPort cannot be less than 0: -1\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckHttpProxyPortProperty_over65535() {\n    System.setProperty(\"http.proxyPort\", \"65536\");\n    System.clearProperty(\"https.proxyPort\");\n    try {\n      JibSystemProperties.checkProxyPortProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"http.proxyPort cannot be greater than 65535: 65536\", ex.getMessage());\n    }\n\n    System.clearProperty(\"http.proxyPort\");\n    System.setProperty(\"https.proxyPort\", \"65536\");\n    try {\n      JibSystemProperties.checkProxyPortProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"https.proxyPort cannot be greater than 65535: 65536\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCheckHttpProxyPortProperty_stringValue() {\n    System.setProperty(\"http.proxyPort\", \"some string\");\n    System.clearProperty(\"https.proxyPort\");\n    try {\n      JibSystemProperties.checkProxyPortProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"http.proxyPort must be an integer: some string\", ex.getMessage());\n    }\n\n    System.clearProperty(\"http.proxyPort\");\n    System.setProperty(\"https.proxyPort\", \"some string\");\n    try {\n      JibSystemProperties.checkProxyPortProperty();\n      Assert.fail();\n    } catch (NumberFormatException ex) {\n      Assert.assertEquals(\"https.proxyPort must be an integer: some string\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testUseBlobMountsPropertyName() {\n    Assert.assertEquals(\"jib.blobMounts\", JibSystemProperties.CROSS_REPOSITORY_BLOB_MOUNTS);\n  }\n\n  @Test\n  public void testUseBlobMounts_undefined() {\n    System.clearProperty(JibSystemProperties.CROSS_REPOSITORY_BLOB_MOUNTS);\n    Assert.assertTrue(JibSystemProperties.useCrossRepositoryBlobMounts());\n  }\n\n  @Test\n  public void testUseBlobMounts_true() {\n    System.setProperty(JibSystemProperties.CROSS_REPOSITORY_BLOB_MOUNTS, \"true\");\n    Assert.assertTrue(JibSystemProperties.useCrossRepositoryBlobMounts());\n  }\n\n  @Test\n  public void testUseBlobMounts_false() {\n    System.setProperty(JibSystemProperties.CROSS_REPOSITORY_BLOB_MOUNTS, \"false\");\n    Assert.assertFalse(JibSystemProperties.useCrossRepositoryBlobMounts());\n  }\n\n  @Test\n  public void testUseBlobMounts_other() {\n    System.setProperty(JibSystemProperties.CROSS_REPOSITORY_BLOB_MOUNTS, \"nonbool\");\n    Assert.assertFalse(JibSystemProperties.useCrossRepositoryBlobMounts());\n  }\n\n  @Test\n  public void testSkipExistingImages_undefined() {\n    System.clearProperty(JibSystemProperties.SKIP_EXISTING_IMAGES);\n    Assert.assertFalse(JibSystemProperties.skipExistingImages());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/hash/CountingDigestOutputStreamTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.hash;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.ByteStreams;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.security.DigestException;\nimport java.util.Map;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link CountingDigestOutputStream}. */\npublic class CountingDigestOutputStreamTest {\n\n  private static final ImmutableMap<String, String> KNOWN_SHA256_HASHES =\n      ImmutableMap.of(\n          \"crepecake\",\n          \"52a9e4d4ba4333ce593707f98564fee1e6d898db0d3602408c0b2a6a424d357c\",\n          \"12345\",\n          \"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5\",\n          \"\",\n          \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\");\n\n  @Test\n  public void test_smokeTest() throws IOException, DigestException {\n    for (Map.Entry<String, String> knownHash : KNOWN_SHA256_HASHES.entrySet()) {\n      String toHash = knownHash.getKey();\n      String expectedHash = knownHash.getValue();\n\n      OutputStream underlyingOutputStream = new ByteArrayOutputStream();\n      CountingDigestOutputStream countingDigestOutputStream =\n          new CountingDigestOutputStream(underlyingOutputStream);\n\n      byte[] bytesToHash = toHash.getBytes(StandardCharsets.UTF_8);\n      InputStream toHashInputStream = new ByteArrayInputStream(bytesToHash);\n      ByteStreams.copy(toHashInputStream, countingDigestOutputStream);\n\n      BlobDescriptor blobDescriptor = countingDigestOutputStream.computeDigest();\n      Assert.assertEquals(DescriptorDigest.fromHash(expectedHash), blobDescriptor.getDigest());\n      Assert.assertEquals(bytesToHash.length, blobDescriptor.getSize());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/FailoverHttpClientTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.api.client.http.GenericUrl;\nimport com.google.api.client.http.HttpHeaders;\nimport com.google.api.client.http.HttpMethods;\nimport com.google.api.client.http.HttpRequest;\nimport com.google.api.client.http.HttpRequestFactory;\nimport com.google.api.client.http.HttpResponse;\nimport com.google.api.client.http.HttpTransport;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.sun.net.httpserver.HttpServer;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.ConnectException;\nimport java.net.InetSocketAddress;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.LongAdder;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLPeerUnverifiedException;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link FailoverHttpClient}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class FailoverHttpClientTest {\n\n  @FunctionalInterface\n  private interface CallFunction {\n\n    Response call(FailoverHttpClient httpClient, URL url, Request request) throws IOException;\n  }\n\n  @Mock private HttpTransport mockHttpTransport;\n  @Mock private HttpTransport mockInsecureHttpTransport;\n  @Mock private HttpRequestFactory mockHttpRequestFactory;\n  @Mock private HttpRequestFactory mockInsecureHttpRequestFactory;\n  @Mock private HttpRequest mockHttpRequest;\n  @Mock private HttpRequest mockInsecureHttpRequest;\n  @Mock private HttpResponse mockHttpResponse;\n  @Mock private Consumer<LogEvent> logger;\n\n  @Captor private ArgumentCaptor<HttpHeaders> httpHeadersCaptor;\n  @Captor private ArgumentCaptor<BlobHttpContent> blobHttpContentCaptor;\n  @Captor private ArgumentCaptor<GenericUrl> urlCaptor;\n\n  private final GenericUrl fakeUrl = new GenericUrl(\"https://crepecake/fake/url\");\n  private final LongAdder totalByteCount = new LongAdder();\n\n  @Before\n  public void setUp() throws IOException {\n    ByteArrayInputStream inStream = new ByteArrayInputStream(new byte[] {'b', 'o', 'd', 'y'});\n    Mockito.when(mockHttpResponse.getContent()).thenReturn(inStream);\n  }\n\n  @Test\n  public void testGet() throws IOException {\n    verifyCall(HttpMethods.GET, FailoverHttpClient::get);\n  }\n\n  @Test\n  public void testPost() throws IOException {\n    verifyCall(HttpMethods.POST, FailoverHttpClient::post);\n  }\n\n  @Test\n  public void testPut() throws IOException {\n    verifyCall(HttpMethods.PUT, FailoverHttpClient::put);\n  }\n\n  @Test\n  public void testHttpTimeout_doNotSetByDefault() throws IOException {\n    try (Response ignored = newHttpClient(false, false).get(fakeUrl.toURL(), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.never()).setConnectTimeout(Mockito.anyInt());\n    Mockito.verify(mockHttpRequest, Mockito.never()).setReadTimeout(Mockito.anyInt());\n  }\n\n  @Test\n  public void testHttpTimeout() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(false, false);\n    try (Response ignored = httpClient.get(fakeUrl.toURL(), fakeRequest(5982))) {\n      // intentionally empty\n    }\n\n    Mockito.verify(mockHttpRequest).setConnectTimeout(5982);\n    Mockito.verify(mockHttpRequest).setReadTimeout(5982);\n  }\n\n  @Test\n  public void testGet_nonHttpsServer_insecureConnectionAndFailoverDisabled()\n      throws MalformedURLException, IOException {\n    FailoverHttpClient httpClient = newHttpClient(false, false);\n    try (Response response = httpClient.get(new URL(\"http://plain.http\"), fakeRequest(null))) {\n      Assert.fail(\"Should disallow non-HTTP attempt\");\n    } catch (SSLException ex) {\n      Assert.assertEquals(\n          \"insecure HTTP connection not allowed: http://plain.http\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCall_secureClientOnUnverifiableServer() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(false, false);\n\n    Mockito.when(mockHttpRequest.execute()).thenThrow(new SSLPeerUnverifiedException(\"unverified\"));\n\n    try (Response response = httpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      Assert.fail(\"Secure caller should fail if cannot verify server\");\n    } catch (SSLException ex) {\n      Assert.assertEquals(\"unverified\", ex.getMessage());\n      Mockito.verifyNoInteractions(logger);\n    }\n  }\n\n  @Test\n  public void testGet_insecureClientOnUnverifiableServer() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute()).thenThrow(new SSLPeerUnverifiedException(\"\"));\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      byte[] bytes = new byte[4];\n      Assert.assertEquals(4, response.getBody().read(bytes));\n      Assert.assertEquals(\"body\", new String(bytes, StandardCharsets.UTF_8));\n    }\n\n    verifyCapturedUrls(\"https://insecure\", \"https://insecure\");\n    verifyWarnings(\n        \"Cannot verify server at https://insecure. Attempting again with no TLS verification.\");\n  }\n\n  @Test\n  public void testGet_insecureClientOnHttpServer() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new SSLException(\"\")) // server is not HTTPS\n        .thenReturn(mockHttpResponse);\n    Mockito.when(mockInsecureHttpRequest.execute())\n        .thenThrow(new SSLException(\"\")); // server is not HTTPS\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      byte[] bytes = new byte[4];\n      Assert.assertEquals(4, response.getBody().read(bytes));\n      Assert.assertEquals(\"body\", new String(bytes, StandardCharsets.UTF_8));\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.times(2)).execute();\n    Mockito.verify(mockInsecureHttpRequest, Mockito.times(1)).execute();\n\n    verifyCapturedUrls(\"https://insecure\", \"https://insecure\", \"http://insecure\");\n    verifyWarnings(\n        \"Cannot verify server at https://insecure. Attempting again with no TLS verification.\",\n        \"Failed to connect to https://insecure over HTTPS. Attempting again with HTTP.\");\n  }\n\n  @Test\n  public void testGet_insecureClientOnHttpServerAndNoPortSpecified() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new ConnectException()) // server is not listening on 443\n        .thenReturn(mockHttpResponse); // respond when connected through 80\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      byte[] bytes = new byte[4];\n      Assert.assertEquals(4, response.getBody().read(bytes));\n      Assert.assertEquals(\"body\", new String(bytes, StandardCharsets.UTF_8));\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.times(2)).execute();\n    Mockito.verifyNoInteractions(mockInsecureHttpRequest);\n\n    verifyCapturedUrls(\"https://insecure\", \"http://insecure\");\n    verifyWarnings(\"Failed to connect to https://insecure over HTTPS. Attempting again with HTTP.\");\n  }\n\n  @Test\n  public void testGet_secureClientOnNonListeningServerAndNoPortSpecified() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(false, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new ConnectException(\"my exception\")); // server not listening on 443\n\n    try (Response response = httpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      Assert.fail(\"Should not fall back to HTTP if secure client\");\n    } catch (ConnectException ex) {\n      Assert.assertEquals(\"my exception\", ex.getMessage());\n\n      verifyCapturedUrls(\"https://insecure\");\n\n      Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();\n      Mockito.verifyNoInteractions(mockInsecureHttpRequest, logger);\n    }\n  }\n\n  @Test\n  public void testGet_insecureClientOnNonListeningServerAndPortSpecified() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new ConnectException(\"my exception\")); // server is not listening on 5000\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"https://insecure:5000\"), fakeRequest(null))) {\n      Assert.fail(\"Should not fall back to HTTP if port was explicitly given and cannot connect\");\n    } catch (ConnectException ex) {\n      Assert.assertEquals(\"my exception\", ex.getMessage());\n\n      verifyCapturedUrls(\"https://insecure:5000\");\n\n      Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();\n      Mockito.verifyNoInteractions(mockInsecureHttpRequest, logger);\n    }\n  }\n\n  @Test\n  public void testGet_timeoutFromConnectException() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute()).thenThrow(new ConnectException(\"Connection timed out\"));\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      Assert.fail(\"Should not fall back to HTTP if timed out even for ConnectionException\");\n    } catch (ConnectException ex) {\n      Assert.assertEquals(\"Connection timed out\", ex.getMessage());\n\n      verifyCapturedUrls(\"https://insecure\");\n\n      Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();\n      Mockito.verifyNoInteractions(mockInsecureHttpRequest, logger);\n    }\n  }\n\n  @Test\n  public void testGet_doNotSendCredentialsOverHttp() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    // make it fall back to HTTP\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new ConnectException()) // server is not listening on 443\n        .thenReturn(mockHttpResponse); // respond when connected through 80\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"https://insecure\"), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    verifyCapturedUrls(\"https://insecure\", \"http://insecure\");\n\n    Assert.assertEquals(2, httpHeadersCaptor.getAllValues().size());\n    Assert.assertEquals(\n        \"Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==\",\n        httpHeadersCaptor.getAllValues().get(0).getAuthorization());\n    Assert.assertNull(httpHeadersCaptor.getAllValues().get(1).getAuthorization());\n  }\n\n  @Test\n  public void testGet_sendCredentialsOverHttp() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, true); // sendCredentialsOverHttp\n\n    try (Response response =\n        insecureHttpClient.get(new URL(\"http://plain.http\"), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Assert.assertEquals(1, urlCaptor.getAllValues().size());\n\n    Assert.assertEquals(\n        \"Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==\",\n        httpHeadersCaptor.getValue().getAuthorization());\n  }\n\n  @Test\n  public void testGet_originalRequestHeaderUntouchedWhenClearingHeader() throws IOException {\n    FailoverHttpClient insecureHttpClient = newHttpClient(true, false);\n\n    Request request = fakeRequest(null);\n    try (Response response = insecureHttpClient.get(new URL(\"http://plain.http\"), request)) {\n      // intentionally empty\n    }\n\n    Assert.assertEquals(1, urlCaptor.getAllValues().size());\n    Assert.assertEquals(1, httpHeadersCaptor.getAllValues().size());\n\n    Assert.assertNull(httpHeadersCaptor.getValue().getAuthorization());\n    Assert.assertEquals(\n        \"Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==\", request.getHeaders().getAuthorization());\n  }\n\n  @Test\n  public void testShutDown() throws IOException {\n    FailoverHttpClient secureHttpClient = newHttpClient(false, false);\n\n    try (Response response = secureHttpClient.get(fakeUrl.toURL(), fakeRequest(null))) {\n      secureHttpClient.shutDown();\n      secureHttpClient.shutDown();\n      Mockito.verify(mockHttpTransport, Mockito.times(1)).shutdown();\n      Mockito.verify(mockHttpResponse, Mockito.times(1)).disconnect();\n    }\n  }\n\n  @Test\n  public void testFollowFailoverHistory_insecureHttps() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new SSLException(\"\"))\n        .thenReturn(mockHttpResponse);\n    try (Response response1 = httpClient.get(new URL(\"https://url\"), fakeRequest(null));\n        Response response2 = httpClient.post(new URL(\"https://url\"), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();\n    Mockito.verify(mockInsecureHttpRequest, Mockito.times(2)).execute();\n    verifyCapturedUrls(\"https://url\", \"https://url\", \"https://url\");\n    verifyWarnings(\n        \"Cannot verify server at https://url. Attempting again with no TLS verification.\");\n  }\n\n  @Test\n  public void testFollowFailoverHistory_httpFailoverByConnectionError() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new ConnectException())\n        .thenReturn(mockHttpResponse);\n\n    try (Response response1 = httpClient.get(new URL(\"https://url\"), fakeRequest(null));\n        Response response2 = httpClient.post(new URL(\"https://url\"), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.times(3)).execute();\n    verifyCapturedUrls(\"https://url\", \"http://url\", \"http://url\");\n    verifyWarnings(\"Failed to connect to https://url over HTTPS. Attempting again with HTTP.\");\n  }\n\n  @Test\n  public void testFollowFailoverHistory_httpFailover() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new SSLException(\"\"))\n        .thenReturn(mockHttpResponse);\n    Mockito.when(mockInsecureHttpRequest.execute()).thenThrow(new SSLException(\"\"));\n\n    try (Response response1 = httpClient.get(new URL(\"https://url:123\"), fakeRequest(null));\n        Response response2 = httpClient.post(new URL(\"https://url:123\"), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.times(3)).execute();\n    Mockito.verify(mockInsecureHttpRequest, Mockito.times(1)).execute();\n    verifyCapturedUrls(\"https://url:123\", \"https://url:123\", \"http://url:123\", \"http://url:123\");\n    verifyWarnings(\n        \"Cannot verify server at https://url:123. Attempting again with no TLS verification.\",\n        \"Failed to connect to https://url:123 over HTTPS. Attempting again with HTTP.\");\n  }\n\n  @Test\n  public void testFollowFailoverHistory_portsDifferent() throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(true, false);\n\n    Mockito.when(mockHttpRequest.execute())\n        .thenThrow(new SSLException(\"\"))\n        .thenThrow(new SSLException(\"\"))\n        .thenReturn(mockHttpResponse);\n\n    try (Response response1 = httpClient.get(new URL(\"https://url:1\"), fakeRequest(null));\n        Response response2 = httpClient.post(new URL(\"https://url:2\"), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Mockito.verify(mockHttpRequest, Mockito.times(2)).execute();\n    Mockito.verify(mockInsecureHttpRequest, Mockito.times(2)).execute();\n    verifyCapturedUrls(\"https://url:1\", \"https://url:1\", \"https://url:2\", \"https://url:2\");\n    verifyWarnings(\n        \"Cannot verify server at https://url:1. Attempting again with no TLS verification.\",\n        \"Cannot verify server at https://url:2. Attempting again with no TLS verification.\");\n  }\n\n  @Test\n  public void testRetries() throws IOException {\n    HttpServer server = HttpServer.create(new InetSocketAddress(0), 1);\n    AtomicBoolean failed = new AtomicBoolean();\n    server\n        .createContext(\"/\")\n        .setHandler(\n            exchange ->\n                exchange.sendResponseHeaders(failed.compareAndSet(false, true) ? 123 : 200, -1));\n    try {\n      server.start();\n      int port = server.getAddress().getPort();\n      List<LogEvent> events = new ArrayList<>();\n      int returnCode =\n          new FailoverHttpClient(true, true, events::add)\n              .get(new URL(\"http://localhost:\" + port), Request.builder().build())\n              .getStatusCode();\n      assertThat(returnCode).isEqualTo(200);\n      assertThat(failed.get()).isTrue();\n      assertThat(events)\n          .containsExactly(\n              LogEvent.warn(\"GET http://localhost:\" + port + \" failed and will be retried\"));\n    } finally {\n      server.stop(0);\n    }\n  }\n\n  private void setUpMocks(\n      HttpTransport mockHttpTransport,\n      HttpRequestFactory mockHttpRequestFactory,\n      HttpRequest mockHttpRequest)\n      throws IOException {\n    Mockito.when(mockHttpTransport.createRequestFactory()).thenReturn(mockHttpRequestFactory);\n    Mockito.when(\n            mockHttpRequestFactory.buildRequest(Mockito.any(), urlCaptor.capture(), Mockito.any()))\n        .thenReturn(mockHttpRequest);\n\n    Mockito.when(mockHttpRequest.setIOExceptionHandler(Mockito.any())).thenReturn(mockHttpRequest);\n    Mockito.when(mockHttpRequest.setUseRawRedirectUrls(Mockito.anyBoolean()))\n        .thenReturn(mockHttpRequest);\n    Mockito.when(mockHttpRequest.setHeaders(httpHeadersCaptor.capture()))\n        .thenReturn(mockHttpRequest);\n    Mockito.when(mockHttpRequest.setConnectTimeout(Mockito.anyInt())).thenReturn(mockHttpRequest);\n    Mockito.when(mockHttpRequest.setReadTimeout(Mockito.anyInt())).thenReturn(mockHttpRequest);\n    Mockito.when(mockHttpRequest.execute()).thenReturn(mockHttpResponse);\n  }\n\n  private FailoverHttpClient newHttpClient(boolean insecure, boolean authOverHttp)\n      throws IOException {\n    setUpMocks(mockHttpTransport, mockHttpRequestFactory, mockHttpRequest);\n    if (insecure) {\n      setUpMocks(\n          mockInsecureHttpTransport, mockInsecureHttpRequestFactory, mockInsecureHttpRequest);\n    }\n    return new FailoverHttpClient(\n        insecure,\n        authOverHttp,\n        logger,\n        () -> mockHttpTransport,\n        () -> mockInsecureHttpTransport,\n        true);\n  }\n\n  private Request fakeRequest(Integer httpTimeout) {\n    return Request.builder()\n        .setAccept(Arrays.asList(\"fake.accept\", \"another.fake.accept\"))\n        .setUserAgent(\"fake user agent\")\n        .setBody(\n            new BlobHttpContent(Blobs.from(\"crepecake\"), \"fake.content.type\", totalByteCount::add))\n        .setAuthorization(Authorization.fromBasicCredentials(\"fake-username\", \"fake-secret\"))\n        .setHttpTimeout(httpTimeout)\n        .build();\n  }\n\n  private void verifyCall(String httpMethod, CallFunction callFunction) throws IOException {\n    FailoverHttpClient httpClient = newHttpClient(false, false);\n    try (Response ignored = callFunction.call(httpClient, fakeUrl.toURL(), fakeRequest(null))) {\n      // intentionally empty\n    }\n\n    Assert.assertEquals(\n        \"fake.accept,another.fake.accept\", httpHeadersCaptor.getValue().getAccept());\n    Assert.assertEquals(\"fake user agent\", httpHeadersCaptor.getValue().getUserAgent());\n    // Base64 representation of \"fake-username:fake-secret\"\n    Assert.assertEquals(\n        \"Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==\",\n        httpHeadersCaptor.getValue().getAuthorization());\n\n    Mockito.verify(mockHttpRequestFactory)\n        .buildRequest(Mockito.eq(httpMethod), Mockito.eq(fakeUrl), blobHttpContentCaptor.capture());\n    Assert.assertEquals(\"fake.content.type\", blobHttpContentCaptor.getValue().getType());\n\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    blobHttpContentCaptor.getValue().writeTo(byteArrayOutputStream);\n\n    Assert.assertEquals(\"crepecake\", byteArrayOutputStream.toString(StandardCharsets.UTF_8.name()));\n    Assert.assertEquals(\"crepecake\".length(), totalByteCount.longValue());\n  }\n\n  private void verifyWarnings(String... logs) {\n    for (String log : logs) {\n      Mockito.verify(logger, Mockito.times(1)).accept(LogEvent.warn(log));\n    }\n    Mockito.verifyNoMoreInteractions(logger);\n  }\n\n  private void verifyCapturedUrls(String... urls) {\n    List<String> captured =\n        urlCaptor.getAllValues().stream().map(GenericUrl::toString).collect(Collectors.toList());\n    Assert.assertEquals(Arrays.asList(urls), captured);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/NotifyingOutputStreamTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.cloud.tools.jib.event.progress.ThrottledAccumulatingConsumer;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Queue;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link NotifyingOutputStream}. */\npublic class NotifyingOutputStreamTest {\n\n  @Test\n  public void testCallback_correctSequence() throws IOException {\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n\n    List<Long> byteCounts = new ArrayList<>();\n\n    try (NotifyingOutputStream notifyingOutputStream =\n        new NotifyingOutputStream(byteArrayOutputStream, byteCounts::add)) {\n      notifyingOutputStream.write(0);\n      notifyingOutputStream.write(new byte[] {1, 2, 3});\n      notifyingOutputStream.write(new byte[] {1, 2, 3, 4, 5}, 3, 2);\n    }\n\n    Assert.assertEquals(Arrays.asList(1L, 3L, 2L), byteCounts);\n    Assert.assertArrayEquals(new byte[] {0, 1, 2, 3, 4, 5}, byteArrayOutputStream.toByteArray());\n  }\n\n  @Test\n  public void testDelay() throws IOException {\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n\n    List<Long> byteCounts = new ArrayList<>();\n\n    Queue<Instant> instantQueue = new ArrayDeque<>();\n    instantQueue.add(Instant.EPOCH);\n\n    try (ThrottledAccumulatingConsumer byteCounter =\n            new ThrottledAccumulatingConsumer(\n                byteCounts::add, Duration.ofSeconds(3), instantQueue::remove);\n        NotifyingOutputStream notifyingOutputStream =\n            new NotifyingOutputStream(byteArrayOutputStream, byteCounter)) {\n      instantQueue.add(Instant.EPOCH);\n      notifyingOutputStream.write(100);\n      instantQueue.add(Instant.EPOCH);\n      notifyingOutputStream.write(new byte[] {101, 102, 103});\n      instantQueue.add(Instant.EPOCH.plusSeconds(4));\n      notifyingOutputStream.write(new byte[] {104, 105, 106});\n\n      instantQueue.add(Instant.EPOCH.plusSeconds(10));\n      notifyingOutputStream.write(new byte[] {107, 108});\n\n      instantQueue.add(Instant.EPOCH.plusSeconds(10));\n      notifyingOutputStream.write(new byte[] {109});\n      instantQueue.add(Instant.EPOCH.plusSeconds(13));\n      notifyingOutputStream.write(new byte[] {0, 110}, 1, 1);\n    }\n\n    Assert.assertEquals(Arrays.asList(7L, 2L, 2L), byteCounts);\n    Assert.assertArrayEquals(\n        new byte[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110},\n        byteArrayOutputStream.toByteArray());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/RequestTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link Request}. */\npublic class RequestTest {\n\n  @Test\n  public void testGetHttpTimeout() {\n    Request request = Request.builder().build();\n\n    Assert.assertNull(request.getHttpTimeout());\n  }\n\n  @Test\n  public void testSetHttpTimeout() {\n    Request request = Request.builder().setHttpTimeout(3000).build();\n\n    Assert.assertEquals(Integer.valueOf(3000), request.getHttpTimeout());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/RequestWrapper.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\n/** Helper to expose package-private methods. */\npublic class RequestWrapper {\n\n  private final Request request;\n\n  public RequestWrapper(Request request) {\n    this.request = request;\n  }\n\n  public int getHttpTimeout() {\n    return request.getHttpTimeout();\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/ResponseTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.api.client.http.HttpResponse;\nimport com.google.common.io.ByteStreams;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link Response}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ResponseTest {\n\n  @Mock private HttpResponse httpResponseMock;\n\n  @Test\n  public void testGetContent() throws IOException {\n    byte[] expectedResponse = \"crepecake\\nis\\ngood!\".getBytes(StandardCharsets.UTF_8);\n    ByteArrayInputStream responseInputStream = new ByteArrayInputStream(expectedResponse);\n\n    Mockito.when(httpResponseMock.getContent()).thenReturn(responseInputStream);\n\n    try (Response response = new Response(httpResponseMock)) {\n      Assert.assertArrayEquals(expectedResponse, ByteStreams.toByteArray(response.getBody()));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/TestWebServer.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport com.google.common.io.Resources;\nimport java.io.BufferedReader;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.GeneralSecurityException;\nimport java.security.KeyStore;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Semaphore;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\n\n/** Simple local web server for testing. */\npublic class TestWebServer implements Closeable {\n\n  private final boolean https;\n  private final int numThreads;\n  private final List<String> responses;\n  private final boolean forgetServedResponses;\n\n  private final ServerSocket serverSocket;\n  private final ExecutorService executorService;\n  private final Semaphore serverStarted = new Semaphore(1);\n  private final StringBuilder inputRead = new StringBuilder();\n\n  private int totalResponsesServed = 0;\n  private int globalResponseIndex = 0;\n\n  public TestWebServer(boolean https)\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    this(https, Arrays.asList(\"HTTP/1.1 200 OK\\nContent-Length:12\\n\\nHello World!\"), 1);\n  }\n\n  public TestWebServer(boolean https, int numThreads)\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    this(https, Arrays.asList(\"HTTP/1.1 200 OK\\nContent-Length:12\\n\\nHello World!\"), numThreads);\n  }\n\n  public TestWebServer(boolean https, List<String> responses, int numThreads)\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    this(https, responses, numThreads, false);\n  }\n\n  @SuppressWarnings(\"FutureReturnValueIgnored\")\n  public TestWebServer(\n      boolean https, List<String> responses, int numThreads, boolean forgetServedResponses)\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    this.https = https;\n    this.responses = responses;\n    this.numThreads = numThreads;\n    this.forgetServedResponses = forgetServedResponses;\n    serverSocket = https ? createHttpsServerSocket() : new ServerSocket(0);\n    executorService = Executors.newFixedThreadPool(numThreads + 1);\n    executorService.submit(this::listen);\n    serverStarted.acquire();\n  }\n\n  public int getLocalPort() {\n    return serverSocket.getLocalPort();\n  }\n\n  public String getEndpoint() {\n    return (https ? \"https\" : \"http\") + \"://localhost:\" + serverSocket.getLocalPort();\n  }\n\n  @Override\n  public void close() throws IOException {\n    serverSocket.close();\n    executorService.shutdown();\n  }\n\n  private ServerSocket createHttpsServerSocket()\n      throws IOException, GeneralSecurityException, URISyntaxException {\n    KeyStore keyStore = KeyStore.getInstance(\"JKS\");\n    // generated with: keytool -genkey -keyalg RSA -keystore ./TestWebServer-keystore\n    Path keyStoreFile = Paths.get(Resources.getResource(\"core/TestWebServer-keystore\").toURI());\n    try (InputStream in = Files.newInputStream(keyStoreFile)) {\n      keyStore.load(in, \"password\".toCharArray());\n    }\n\n    KeyManagerFactory keyManagerFactory =\n        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n    keyManagerFactory.init(keyStore, \"password\".toCharArray());\n\n    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n    sslContext.init(keyManagerFactory.getKeyManagers(), null, null);\n    return sslContext.getServerSocketFactory().createServerSocket(0);\n  }\n\n  @SuppressWarnings(\"FutureReturnValueIgnored\")\n  private Void listen() throws IOException {\n    serverStarted.release();\n    for (int i = 0; i < numThreads; i++) {\n      Socket socket = serverSocket.accept();\n      executorService.submit(() -> serveResponses(socket));\n    }\n    return null;\n  }\n\n  private Void serveResponses(Socket socket) throws IOException {\n    try (Socket ignored = socket) {\n      InputStream in = socket.getInputStream();\n      OutputStream out = socket.getOutputStream();\n\n      int firstByte = in.read();\n      int secondByte = in.read();\n      if (!(firstByte == 'G' && secondByte == 'E')\n          && !(firstByte == 'P' && secondByte == 'O')\n          && !(firstByte == 'H' && secondByte == 'E')) { // GET, POST, HEAD, ...\n        out.write(\n            \"HTTP/1.1 400 Bad Request\\nContent-Length: 0\\n\\n\".getBytes(StandardCharsets.UTF_8));\n        return null;\n      }\n\n      BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));\n      for (int i = 0; true; i++) {\n        for (String line = reader.readLine();\n            line != null && !line.isEmpty(); // An empty line marks the end of an HTTP request.\n            line = reader.readLine()) {\n          synchronized (inputRead) {\n            if (firstByte != -1) {\n              inputRead.append((char) firstByte).append((char) secondByte);\n              firstByte = -1;\n            }\n            inputRead.append(line).append('\\n');\n          }\n        }\n        String response = getNextResponse(i);\n        if (response == null) {\n          return null;\n        }\n        out.write(response.getBytes(StandardCharsets.UTF_8));\n        out.flush();\n      }\n    }\n  }\n\n  private synchronized String getNextResponse(int index) {\n    if (index >= responses.size() || globalResponseIndex >= responses.size()) {\n      return null;\n    }\n    totalResponsesServed++;\n    return forgetServedResponses ? responses.get(globalResponseIndex++) : responses.get(index);\n  }\n\n  /**\n   * Returns input read. Note if there were concurrent connections, input lines from different\n   * connections can be intermixed. However, no lines will ever be broken in the middle.\n   */\n  public String getInputRead() {\n    synchronized (inputRead) {\n      return inputRead.toString();\n    }\n  }\n\n  public synchronized int getTotalResponsesServed() {\n    return totalResponsesServed;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/http/WithServerFailoverHttpClientTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.http;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.common.io.ByteStreams;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.security.GeneralSecurityException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport javax.net.ssl.SSLException;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link FailoverHttpClient} using an actual local server. */\n@RunWith(MockitoJUnitRunner.class)\npublic class WithServerFailoverHttpClientTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  @Mock private Consumer<LogEvent> logger;\n\n  private final Request request = new Request.Builder().build();\n\n  @Test\n  public void testGet()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    FailoverHttpClient insecureHttpClient =\n        new FailoverHttpClient(true /*insecure*/, false, logger);\n    try (TestWebServer server = new TestWebServer(false);\n        Response response = insecureHttpClient.get(new URL(server.getEndpoint()), request)) {\n\n      Assert.assertEquals(200, response.getStatusCode());\n      Assert.assertArrayEquals(\n          \"Hello World!\".getBytes(StandardCharsets.UTF_8),\n          ByteStreams.toByteArray(response.getBody()));\n      Mockito.verifyNoInteractions(logger);\n    }\n  }\n\n  @Test\n  public void testSecureConnectionOnInsecureHttpsServer()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    FailoverHttpClient secureHttpClient =\n        new FailoverHttpClient(false /*secure*/, false, logger, false);\n    try (TestWebServer server = new TestWebServer(true);\n        Response ignored = secureHttpClient.get(new URL(server.getEndpoint()), request)) {\n      Assert.fail(\"Should fail if cannot verify peer\");\n\n    } catch (SSLException ex) {\n      Assert.assertNotNull(ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testInsecureConnection_insecureHttpsFailover()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    FailoverHttpClient insecureHttpClient =\n        new FailoverHttpClient(true /*insecure*/, false, logger);\n    try (TestWebServer server = new TestWebServer(true, 2);\n        Response response = insecureHttpClient.get(new URL(server.getEndpoint()), request)) {\n\n      Assert.assertEquals(200, response.getStatusCode());\n      Assert.assertArrayEquals(\n          \"Hello World!\".getBytes(StandardCharsets.UTF_8),\n          ByteStreams.toByteArray(response.getBody()));\n\n      String endpoint = server.getEndpoint();\n      String expectedLog =\n          \"Cannot verify server at \" + endpoint + \". Attempting again with no TLS verification.\";\n      Mockito.verify(logger).accept(LogEvent.warn(expectedLog));\n    }\n  }\n\n  @Test\n  public void testInsecureConnection_plainHttpFailover()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    FailoverHttpClient insecureHttpClient =\n        new FailoverHttpClient(true /*insecure*/, false, logger);\n    try (TestWebServer server = new TestWebServer(false, 3)) {\n      String httpsUrl = server.getEndpoint().replace(\"http://\", \"https://\");\n      try (Response response = insecureHttpClient.get(new URL(httpsUrl), request)) {\n\n        Assert.assertEquals(200, response.getStatusCode());\n        Assert.assertArrayEquals(\n            \"Hello World!\".getBytes(StandardCharsets.UTF_8),\n            ByteStreams.toByteArray(response.getBody()));\n\n        String expectedLog1 =\n            \"Cannot verify server at \" + httpsUrl + \". Attempting again with no TLS verification.\";\n        String expectedLog2 =\n            \"Failed to connect to \" + httpsUrl + \" over HTTPS. Attempting again with HTTP.\";\n        Mockito.verify(logger).accept(LogEvent.warn(expectedLog1));\n        Mockito.verify(logger).accept(LogEvent.warn(expectedLog2));\n      }\n    }\n  }\n\n  @Test\n  public void testProxyCredentialProperties()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    String proxyResponse =\n        \"HTTP/1.1 407 Proxy Authentication Required\\n\"\n            + \"Proxy-Authenticate: BASIC realm=\\\"some-realm\\\"\\n\"\n            + \"Cache-Control: no-cache\\n\"\n            + \"Pragma: no-cache\\n\"\n            + \"Content-Length: 0\\n\\n\";\n    String targetServerResponse = \"HTTP/1.1 200 OK\\nContent-Length:12\\n\\nHello World!\";\n\n    FailoverHttpClient httpClient = new FailoverHttpClient(true /*insecure*/, false, logger);\n    try (TestWebServer server =\n        new TestWebServer(false, Arrays.asList(proxyResponse, targetServerResponse), 1)) {\n      System.setProperty(\"http.proxyHost\", \"localhost\");\n      System.setProperty(\"http.proxyPort\", String.valueOf(server.getLocalPort()));\n      System.setProperty(\"http.proxyUser\", \"user_sys_prop\");\n      System.setProperty(\"http.proxyPassword\", \"pass_sys_prop\");\n\n      try (Response response = httpClient.get(new URL(\"http://does.not.matter\"), request)) {\n        MatcherAssert.assertThat(\n            server.getInputRead(),\n            CoreMatchers.containsString(\n                \"Proxy-Authorization: Basic dXNlcl9zeXNfcHJvcDpwYXNzX3N5c19wcm9w\"));\n        Assert.assertArrayEquals(\n            \"Hello World!\".getBytes(StandardCharsets.UTF_8),\n            ByteStreams.toByteArray(response.getBody()));\n      }\n    }\n  }\n\n  @Test\n  public void testClosingResourcesMultipleTimes_noErrors()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    FailoverHttpClient httpClient = new FailoverHttpClient(true /*insecure*/, false, logger);\n    try (TestWebServer server = new TestWebServer(false, 2);\n        Response ignored1 = httpClient.get(new URL(server.getEndpoint()), request);\n        Response ignored2 = httpClient.get(new URL(server.getEndpoint()), request)) {\n      ignored1.close();\n      ignored2.close();\n    } finally {\n\n      // Validate that calling shutdown() many times completes with no errors\n      assertThat(httpClient.getTransportsCreated()).hasSize(2);\n      httpClient.shutDown();\n      httpClient.shutDown();\n      assertThat(httpClient.getTransportsCreated()).hasSize(0);\n    }\n  }\n\n  @Test\n  public void testRedirectionUrls()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    // Sample query strings from\n    // https://github.com/GoogleContainerTools/jib/issues/1986#issuecomment-547610104\n    String url1 = \"?id=301&_auth_=exp=1572285389~hmac=f0a387f0\";\n    String url2 = \"?id=302&Signature=2wYOD0a%2BDAkK%2F9lQJUOuIpYti8o%3D&Expires=1569997614\";\n    String url3 = \"?id=303&_auth_=exp=1572285389~hmac=f0a387f0\";\n    String url4 = \"?id=307&Signature=2wYOD0a%2BDAkK%2F9lQJUOuIpYti8o%3D&Expires=1569997614\";\n    String url5 = \"?id=308&_auth_=exp=1572285389~hmac=f0a387f0\";\n\n    String redirect301 =\n        \"HTTP/1.1 301 Moved Permanently\\nLocation: \" + url1 + \"\\nContent-Length: 0\\n\\n\";\n    String redirect302 = \"HTTP/1.1 302 Found\\nLocation: \" + url2 + \"\\nContent-Length: 0\\n\\n\";\n    String redirect303 = \"HTTP/1.1 303 See Other\\nLocation: \" + url3 + \"\\nContent-Length: 0\\n\\n\";\n    String redirect307 =\n        \"HTTP/1.1 307 Temporary Redirect\\nLocation: \" + url4 + \"\\nContent-Length: 0\\n\\n\";\n    String redirect308 =\n        \"HTTP/1.1 308 Permanent Redirect\\nLocation: \" + url5 + \"\\nContent-Length: 0\\n\\n\";\n    String ok200 = \"HTTP/1.1 200 OK\\nContent-Length:12\\n\\nHello World!\";\n    List<String> responses =\n        Arrays.asList(redirect301, redirect302, redirect303, redirect307, redirect308, ok200);\n\n    FailoverHttpClient httpClient = new FailoverHttpClient(true /*insecure*/, false, logger);\n    try (TestWebServer server = new TestWebServer(false, responses, 1)) {\n      httpClient.get(new URL(server.getEndpoint()), request);\n\n      MatcherAssert.assertThat(\n          server.getInputRead(),\n          CoreMatchers.containsString(\"GET /?id=301&_auth_=exp=1572285389~hmac=f0a387f0 \"));\n      MatcherAssert.assertThat(\n          server.getInputRead(),\n          CoreMatchers.containsString(\n              \"GET /?id=302&Signature=2wYOD0a%2BDAkK%2F9lQJUOuIpYti8o%3D&Expires=1569997614 \"));\n      MatcherAssert.assertThat(\n          server.getInputRead(),\n          CoreMatchers.containsString(\"GET /?id=303&_auth_=exp=1572285389~hmac=f0a387f0 \"));\n      MatcherAssert.assertThat(\n          server.getInputRead(),\n          CoreMatchers.containsString(\n              \"GET /?id=307&Signature=2wYOD0a%2BDAkK%2F9lQJUOuIpYti8o%3D&Expires=1569997614 \"));\n      MatcherAssert.assertThat(\n          server.getInputRead(),\n          CoreMatchers.containsString(\"GET /?id=308&_auth_=exp=1572285389~hmac=f0a387f0 \"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/ImageTarballTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.docker.json.DockerManifestEntryTemplate;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.CharStreams;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ImageTarball}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ImageTarballTest {\n\n  private Path fileA;\n  private Path fileB;\n  private DescriptorDigest fakeDigestA;\n  private DescriptorDigest fakeDigestB;\n\n  @Mock private Layer mockLayer1;\n  @Mock private Layer mockLayer2;\n\n  @Before\n  public void setup() throws URISyntaxException, IOException, DigestException {\n    fileA = Paths.get(Resources.getResource(\"core/fileA\").toURI());\n    fileB = Paths.get(Resources.getResource(\"core/fileB\").toURI());\n    long fileASize = Files.size(fileA);\n    long fileBSize = Files.size(fileB);\n\n    fakeDigestA =\n        DescriptorDigest.fromHash(\n            \"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5\");\n    fakeDigestB =\n        DescriptorDigest.fromHash(\n            \"5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc6\");\n\n    Mockito.when(mockLayer1.getBlob()).thenReturn(Blobs.from(fileA));\n    Mockito.when(mockLayer1.getBlobDescriptor())\n        .thenReturn(new BlobDescriptor(fileASize, fakeDigestA));\n    Mockito.when(mockLayer1.getDiffId()).thenReturn(fakeDigestA);\n    Mockito.when(mockLayer2.getBlob()).thenReturn(Blobs.from(fileB));\n    Mockito.when(mockLayer2.getBlobDescriptor())\n        .thenReturn(new BlobDescriptor(fileBSize, fakeDigestB));\n    Mockito.when(mockLayer2.getDiffId()).thenReturn(fakeDigestB);\n  }\n\n  @Test\n  public void testWriteTo_docker()\n      throws InvalidImageReferenceException, IOException, LayerPropertyNotFoundException {\n    Image testImage =\n        Image.builder(V22ManifestTemplate.class).addLayer(mockLayer1).addLayer(mockLayer2).build();\n    ImageTarball imageTarball =\n        new ImageTarball(\n            testImage,\n            ImageReference.parse(\"my/image:tag\"),\n            ImmutableSet.of(\"tag\", \"another-tag\", \"tag3\"));\n\n    ByteArrayOutputStream out = new ByteArrayOutputStream();\n    imageTarball.writeTo(out);\n    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());\n    try (TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(in)) {\n\n      // Verifies layer with fileA was added.\n      TarArchiveEntry headerFileALayer = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(fakeDigestA.getHash() + \".tar.gz\", headerFileALayer.getName());\n      String fileAString =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      Assert.assertEquals(Blobs.writeToString(Blobs.from(fileA)), fileAString);\n\n      // Verifies layer with fileB was added.\n      TarArchiveEntry headerFileBLayer = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(fakeDigestB.getHash() + \".tar.gz\", headerFileBLayer.getName());\n      String fileBString =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      Assert.assertEquals(Blobs.writeToString(Blobs.from(fileB)), fileBString);\n\n      // Verifies container configuration was added.\n      TarArchiveEntry headerContainerConfiguration = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\"config.json\", headerContainerConfiguration.getName());\n      String containerConfigJson =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      JsonTemplateMapper.readJson(containerConfigJson, ContainerConfigurationTemplate.class);\n\n      // Verifies manifest was added.\n      TarArchiveEntry headerManifest = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\"manifest.json\", headerManifest.getName());\n      String manifestJson =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      DockerManifestEntryTemplate manifest =\n          JsonTemplateMapper.readListOfJson(manifestJson, DockerManifestEntryTemplate.class).get(0);\n      Assert.assertEquals(\n          ImmutableList.of(\"my/image:tag\", \"my/image:another-tag\", \"my/image:tag3\"),\n          manifest.getRepoTags());\n    }\n  }\n\n  @Test\n  public void testWriteTo_oci()\n      throws InvalidImageReferenceException, IOException, LayerPropertyNotFoundException {\n    Image testImage =\n        Image.builder(OciManifestTemplate.class).addLayer(mockLayer1).addLayer(mockLayer2).build();\n    ImageTarball imageTarball =\n        new ImageTarball(\n            testImage,\n            ImageReference.parse(\"my/image:tag\"),\n            ImmutableSet.of(\"tag\", \"another-tag\", \"tag3\"));\n\n    ByteArrayOutputStream out = new ByteArrayOutputStream();\n    imageTarball.writeTo(out);\n    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());\n    try (TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(in)) {\n\n      // Verifies layer with fileA was added.\n      TarArchiveEntry headerFileALayer = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\"blobs/sha256/\" + fakeDigestA.getHash(), headerFileALayer.getName());\n      String fileAString =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      Assert.assertEquals(Blobs.writeToString(Blobs.from(fileA)), fileAString);\n\n      // Verifies layer with fileB was added.\n      TarArchiveEntry headerFileBLayer = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\"blobs/sha256/\" + fakeDigestB.getHash(), headerFileBLayer.getName());\n      String fileBString =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      Assert.assertEquals(Blobs.writeToString(Blobs.from(fileB)), fileBString);\n\n      // Verifies container configuration was added.\n      TarArchiveEntry headerContainerConfiguration = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\n          \"blobs/sha256/011212cff4d5d6b18c7d3a00a7a2701514a1fdd3ec0d250a03756f84f3d955d4\",\n          headerContainerConfiguration.getName());\n      JsonTemplateMapper.readJson(tarArchiveInputStream, ContainerConfigurationTemplate.class);\n\n      // Verifies manifest was added.\n      TarArchiveEntry headerManifest = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\n          \"blobs/sha256/1543d061159a8d6877087938bfd62681cdeff873e1fa3e1fcf12dec358c112a4\",\n          headerManifest.getName());\n      JsonTemplateMapper.readJson(tarArchiveInputStream, OciManifestTemplate.class);\n\n      // Verifies oci-layout was added.\n      TarArchiveEntry headerOciLayout = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\"oci-layout\", headerOciLayout.getName());\n      String ociLayoutJson =\n          CharStreams.toString(\n              new InputStreamReader(tarArchiveInputStream, StandardCharsets.UTF_8));\n      Assert.assertEquals(\"{\\\"imageLayoutVersion\\\": \\\"1.0.0\\\"}\", ociLayoutJson);\n\n      // Verifies index.json was added.\n      TarArchiveEntry headerIndex = tarArchiveInputStream.getNextEntry();\n      Assert.assertEquals(\"index.json\", headerIndex.getName());\n      OciIndexTemplate index =\n          JsonTemplateMapper.readJson(tarArchiveInputStream, OciIndexTemplate.class);\n      BuildableManifestTemplate.ContentDescriptorTemplate indexManifest =\n          index.getManifests().get(0);\n      Assert.assertEquals(\n          \"1543d061159a8d6877087938bfd62681cdeff873e1fa3e1fcf12dec358c112a4\",\n          indexManifest.getDigest().getHash());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/ImageTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link Image}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ImageTest {\n\n  @Mock private Layer mockLayer;\n  @Mock private DescriptorDigest mockDescriptorDigest;\n\n  @Before\n  public void setUp() throws LayerPropertyNotFoundException {\n    Mockito.when(mockLayer.getBlobDescriptor())\n        .thenReturn(new BlobDescriptor(mockDescriptorDigest));\n  }\n\n  @Test\n  public void test_smokeTest() throws LayerPropertyNotFoundException {\n    Image image =\n        Image.builder(V22ManifestTemplate.class)\n            .setCreated(Instant.ofEpochSecond(10000))\n            .addEnvironmentVariable(\"crepecake\", \"is great\")\n            .addEnvironmentVariable(\"VARIABLE\", \"VALUE\")\n            .setEntrypoint(Arrays.asList(\"some\", \"command\"))\n            .setProgramArguments(Arrays.asList(\"arg1\", \"arg2\"))\n            .addExposedPorts(ImmutableSet.of(Port.tcp(1000), Port.tcp(2000)))\n            .addVolumes(\n                ImmutableSet.of(\n                    AbsoluteUnixPath.get(\"/a/path\"), AbsoluteUnixPath.get(\"/another/path\")))\n            .setUser(\"john\")\n            .addLayer(mockLayer)\n            .build();\n\n    Assert.assertEquals(V22ManifestTemplate.class, image.getImageFormat());\n    Assert.assertEquals(\n        mockDescriptorDigest, image.getLayers().get(0).getBlobDescriptor().getDigest());\n    Assert.assertEquals(Instant.ofEpochSecond(10000), image.getCreated());\n    Assert.assertEquals(\n        ImmutableMap.of(\"crepecake\", \"is great\", \"VARIABLE\", \"VALUE\"), image.getEnvironment());\n    Assert.assertEquals(Arrays.asList(\"some\", \"command\"), image.getEntrypoint());\n    Assert.assertEquals(Arrays.asList(\"arg1\", \"arg2\"), image.getProgramArguments());\n    Assert.assertEquals(ImmutableSet.of(Port.tcp(1000), Port.tcp(2000)), image.getExposedPorts());\n    Assert.assertEquals(\n        ImmutableSet.of(AbsoluteUnixPath.get(\"/a/path\"), AbsoluteUnixPath.get(\"/another/path\")),\n        image.getVolumes());\n    Assert.assertEquals(\"john\", image.getUser());\n  }\n\n  @Test\n  public void testDefaults() {\n    Image image = Image.builder(V22ManifestTemplate.class).build();\n    Assert.assertEquals(\"amd64\", image.getArchitecture());\n    Assert.assertEquals(\"linux\", image.getOs());\n    Assert.assertEquals(Collections.emptyList(), image.getLayers());\n    Assert.assertEquals(Collections.emptyList(), image.getHistory());\n  }\n\n  @Test\n  public void testOsArch() {\n    Image image =\n        Image.builder(V22ManifestTemplate.class).setArchitecture(\"wasm\").setOs(\"js\").build();\n    Assert.assertEquals(\"wasm\", image.getArchitecture());\n    Assert.assertEquals(\"js\", image.getOs());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/LayerTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link Layer}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class LayerTest {\n\n  @Mock private DescriptorDigest mockDescriptorDigest;\n  @Mock private BlobDescriptor mockBlobDescriptor;\n  @Mock private DescriptorDigest mockDiffId;\n\n  @Test\n  public void testNew_reference() throws LayerPropertyNotFoundException {\n    Layer layer = new ReferenceLayer(mockBlobDescriptor, mockDiffId);\n\n    try {\n      layer.getBlob();\n      Assert.fail(\"Blob content should not be available for reference layer\");\n    } catch (LayerPropertyNotFoundException ex) {\n      Assert.assertEquals(\"Blob not available for reference layer\", ex.getMessage());\n    }\n\n    Assert.assertEquals(mockBlobDescriptor, layer.getBlobDescriptor());\n    Assert.assertEquals(mockDiffId, layer.getDiffId());\n  }\n\n  @Test\n  public void testNew_digestOnly() throws LayerPropertyNotFoundException {\n    Layer layer = new DigestOnlyLayer(mockDescriptorDigest);\n\n    try {\n      layer.getBlob();\n      Assert.fail(\"Blob content should not be available for digest-only layer\");\n    } catch (LayerPropertyNotFoundException ex) {\n      Assert.assertEquals(\"Blob not available for digest-only layer\", ex.getMessage());\n    }\n\n    Assert.assertFalse(layer.getBlobDescriptor().hasSize());\n    Assert.assertEquals(mockDescriptorDigest, layer.getBlobDescriptor().getDigest());\n\n    try {\n      layer.getDiffId();\n      Assert.fail(\"Diff ID should not be available for digest-only layer\");\n    } catch (LayerPropertyNotFoundException ex) {\n      Assert.assertEquals(\"Diff ID not available for digest-only layer\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.StandardOpenOption;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link ReproducibleLayerBuilder}. */\npublic class ReproducibleLayerBuilderTest {\n\n  /**\n   * Verifies the correctness of the next {@link TarArchiveEntry} in the {@link\n   * TarArchiveInputStream}.\n   *\n   * @param tarArchiveInputStream the {@link TarArchiveInputStream} to read from\n   * @param expectedExtractionPath the expected extraction path of the next entry\n   * @param expectedFile the file to match against the contents of the next entry\n   * @throws IOException if an I/O exception occurs\n   */\n  private static void verifyNextTarArchiveEntry(\n      TarArchiveInputStream tarArchiveInputStream, String expectedExtractionPath, Path expectedFile)\n      throws IOException {\n    TarArchiveEntry header = tarArchiveInputStream.getNextEntry();\n    assertThat(header.getName()).isEqualTo(expectedExtractionPath);\n\n    byte[] expectedBytes = Files.readAllBytes(expectedFile);\n    byte[] extractedBytes = ByteStreams.toByteArray(tarArchiveInputStream);\n    assertThat(extractedBytes).isEqualTo(expectedBytes);\n  }\n\n  /**\n   * Verifies that the next {@link TarArchiveEntry} in the {@link TarArchiveInputStream} is a\n   * directory with correct permissions.\n   *\n   * @param tarArchiveInputStream the {@link TarArchiveInputStream} to read from\n   * @param expectedExtractionPath the expected extraction path of the next entry\n   * @throws IOException if an I/O exception occurs\n   */\n  private static void verifyNextTarArchiveEntryIsDirectory(\n      TarArchiveInputStream tarArchiveInputStream, String expectedExtractionPath)\n      throws IOException {\n    TarArchiveEntry extractionPathEntry = tarArchiveInputStream.getNextEntry();\n    assertThat(extractionPathEntry.getName()).isEqualTo(expectedExtractionPath);\n    assertThat(extractionPathEntry.isDirectory()).isTrue();\n    assertThat(extractionPathEntry.getMode()).isEqualTo(TarArchiveEntry.DEFAULT_DIR_MODE);\n  }\n\n  private static FileEntry defaultLayerEntry(Path source, AbsoluteUnixPath destination) {\n    return new FileEntry(\n        source,\n        destination,\n        FileEntriesLayer.DEFAULT_FILE_PERMISSIONS_PROVIDER.get(source, destination),\n        FileEntriesLayer.DEFAULT_MODIFICATION_TIME);\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testBuild() throws URISyntaxException, IOException {\n    Path layerDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    Path blobA = Paths.get(Resources.getResource(\"core/blobA\").toURI());\n\n    ReproducibleLayerBuilder layerBuilder =\n        new ReproducibleLayerBuilder(\n            ImmutableList.copyOf(\n                FileEntriesLayer.builder()\n                    .addEntryRecursive(\n                        layerDirectory, AbsoluteUnixPath.get(\"/extract/here/apple/layer\"))\n                    .addEntry(blobA, AbsoluteUnixPath.get(\"/extract/here/apple/blobA\"))\n                    .addEntry(blobA, AbsoluteUnixPath.get(\"/extract/here/banana/blobA\"))\n                    .build()\n                    .getEntries()));\n\n    // Writes the layer tar to a temporary file.\n    Blob unwrittenBlob = layerBuilder.build();\n    Path temporaryFile = temporaryFolder.newFile().toPath();\n    try (OutputStream temporaryFileOutputStream =\n        new BufferedOutputStream(Files.newOutputStream(temporaryFile))) {\n      unwrittenBlob.writeTo(temporaryFileOutputStream);\n    }\n\n    // Reads the file back.\n    try (TarArchiveInputStream tarArchiveInputStream =\n        new TarArchiveInputStream(Files.newInputStream(temporaryFile))) {\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/\");\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/\");\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/apple/\");\n      verifyNextTarArchiveEntry(tarArchiveInputStream, \"extract/here/apple/blobA\", blobA);\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/apple/layer/\");\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/apple/layer/a/\");\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/apple/layer/a/b/\");\n      verifyNextTarArchiveEntry(\n          tarArchiveInputStream,\n          \"extract/here/apple/layer/a/b/bar\",\n          Paths.get(Resources.getResource(\"core/layer/a/b/bar\").toURI()));\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/apple/layer/c/\");\n      verifyNextTarArchiveEntry(\n          tarArchiveInputStream,\n          \"extract/here/apple/layer/c/cat\",\n          Paths.get(Resources.getResource(\"core/layer/c/cat\").toURI()));\n      verifyNextTarArchiveEntry(\n          tarArchiveInputStream,\n          \"extract/here/apple/layer/foo\",\n          Paths.get(Resources.getResource(\"core/layer/foo\").toURI()));\n      verifyNextTarArchiveEntryIsDirectory(tarArchiveInputStream, \"extract/here/banana/\");\n      verifyNextTarArchiveEntry(tarArchiveInputStream, \"extract/here/banana/blobA\", blobA);\n    }\n  }\n\n  @Test\n  public void testToBlob_reproducibility() throws IOException {\n    Path testRoot = temporaryFolder.getRoot().toPath();\n    Path root1 = Files.createDirectories(testRoot.resolve(\"files1\"));\n    Path root2 = Files.createDirectories(testRoot.resolve(\"files2\"));\n\n    // TODO: Currently this test only covers variation in order and modification time, even though\n    // TODO: the code is designed to clean up userid/groupid, this test does not check that yet.\n    String contentA = \"abcabc\";\n    Path fileA1 = createFile(root1, \"fileA\", contentA, 10000);\n    Path fileA2 = createFile(root2, \"fileA\", contentA, 20000);\n    String contentB = \"yumyum\";\n    Path fileB1 = createFile(root1, \"fileB\", contentB, 10000);\n    Path fileB2 = createFile(root2, \"fileB\", contentB, 20000);\n\n    // check if modification times are off\n    assertThat(Files.getLastModifiedTime(fileA2)).isNotEqualTo(Files.getLastModifiedTime(fileA1));\n    assertThat(Files.getLastModifiedTime(fileB2)).isNotEqualTo(Files.getLastModifiedTime(fileB1));\n\n    // create layers of exact same content but ordered differently and with different timestamps\n    Blob layer =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(\n                    defaultLayerEntry(fileA1, AbsoluteUnixPath.get(\"/somewhere/fileA\")),\n                    defaultLayerEntry(fileB1, AbsoluteUnixPath.get(\"/somewhere/fileB\"))))\n            .build();\n    Blob reproduced =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(\n                    defaultLayerEntry(fileB2, AbsoluteUnixPath.get(\"/somewhere/fileB\")),\n                    defaultLayerEntry(fileA2, AbsoluteUnixPath.get(\"/somewhere/fileA\"))))\n            .build();\n\n    byte[] layerContent = Blobs.writeToByteArray(layer);\n    byte[] reproducedLayerContent = Blobs.writeToByteArray(reproduced);\n\n    assertThat(layerContent).isEqualTo(reproducedLayerContent);\n  }\n\n  @Test\n  public void testBuild_parentDirBehavior() throws IOException {\n    Path testRoot = temporaryFolder.getRoot().toPath();\n\n    // the path doesn't really matter on source files, but these are structured\n    Path parent = Files.createDirectories(testRoot.resolve(\"dirA\"));\n    Path fileA = Files.createFile(parent.resolve(\"fileA\"));\n    Path ignoredParent = Files.createDirectories(testRoot.resolve(\"dirB-ignored\"));\n    Path fileB = Files.createFile(ignoredParent.resolve(\"fileB\"));\n    Path fileC =\n        Files.createFile(Files.createDirectories(testRoot.resolve(\"dirC-absent\")).resolve(\"fileC\"));\n\n    Blob layer =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(\n                    new FileEntry(\n                        parent,\n                        AbsoluteUnixPath.get(\"/root/dirA\"),\n                        FilePermissions.fromOctalString(\"111\"),\n                        Instant.ofEpochSecond(10)),\n                    new FileEntry(\n                        fileA,\n                        AbsoluteUnixPath.get(\"/root/dirA/fileA\"),\n                        FilePermissions.fromOctalString(\"222\"),\n                        Instant.ofEpochSecond(20)),\n                    new FileEntry(\n                        fileB,\n                        AbsoluteUnixPath.get(\"/root/dirB-ignored/fileB\"),\n                        FilePermissions.fromOctalString(\"333\"),\n                        Instant.ofEpochSecond(30)),\n                    new FileEntry(\n                        ignoredParent,\n                        AbsoluteUnixPath.get(\"/root/dirB-ignored\"),\n                        FilePermissions.fromOctalString(\"444\"),\n                        Instant.ofEpochSecond(40)),\n                    new FileEntry(\n                        fileC,\n                        AbsoluteUnixPath.get(\"/root/dirC-absent/file3\"),\n                        FilePermissions.fromOctalString(\"555\"),\n                        Instant.ofEpochSecond(50))))\n            .build();\n\n    Path tarFile = temporaryFolder.newFile().toPath();\n    try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) {\n      layer.writeTo(out);\n    }\n\n    try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) {\n      // root (default folder permissions)\n      TarArchiveEntry root = in.getNextEntry();\n      assertThat(root.getMode()).isEqualTo(040755);\n      assertThat(root.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1));\n      assertThat(root.getLongUserId()).isEqualTo(0);\n      assertThat(root.getLongGroupId()).isEqualTo(0);\n      assertThat(root.getUserName()).isEmpty();\n      assertThat(root.getGroupName()).isEmpty();\n\n      // parentAAA (custom permissions, custom timestamp)\n      TarArchiveEntry rootParentA = in.getNextEntry();\n      assertThat(rootParentA.getMode()).isEqualTo(040111);\n      assertThat(rootParentA.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(10));\n      assertThat(rootParentA.getLongUserId()).isEqualTo(0);\n      assertThat(rootParentA.getLongGroupId()).isEqualTo(0);\n      assertThat(rootParentA.getUserName()).isEmpty();\n      assertThat(rootParentA.getGroupName()).isEmpty();\n\n      // skip over fileA\n      in.getNextEntry();\n\n      // parentBBB (default permissions - ignored custom permissions, since fileB added first)\n      TarArchiveEntry rootParentB = in.getNextEntry();\n      // TODO (#1650): we want 040444 here.\n      assertThat(rootParentB.getMode()).isEqualTo(040755);\n      // TODO (#1650): we want Instant.ofEpochSecond(40) here.\n      assertThat(rootParentB.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1));\n      assertThat(rootParentB.getLongUserId()).isEqualTo(0);\n      assertThat(rootParentB.getLongGroupId()).isEqualTo(0);\n      assertThat(rootParentB.getUserName()).isEmpty();\n      assertThat(rootParentB.getGroupName()).isEmpty();\n\n      // skip over fileB\n      in.getNextEntry();\n\n      // parentCCC (default permissions - no entry provided)\n      TarArchiveEntry rootParentC = in.getNextEntry();\n      assertThat(rootParentC.getMode()).isEqualTo(040755);\n      assertThat(rootParentC.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1));\n      assertThat(rootParentC.getLongUserId()).isEqualTo(0);\n      assertThat(rootParentC.getLongGroupId()).isEqualTo(0);\n      assertThat(rootParentC.getUserName()).isEmpty();\n      assertThat(rootParentC.getGroupName()).isEmpty();\n\n      // we don't care about fileC\n    }\n  }\n\n  @Test\n  public void testBuild_timestampDefault() throws IOException {\n    Path file = createFile(temporaryFolder.getRoot().toPath(), \"fileA\", \"some content\", 54321);\n\n    Blob blob =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(defaultLayerEntry(file, AbsoluteUnixPath.get(\"/fileA\"))))\n            .build();\n\n    Path tarFile = temporaryFolder.newFile().toPath();\n    try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) {\n      blob.writeTo(out);\n    }\n\n    // Reads the file back.\n    try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) {\n      assertThat(in.getNextEntry().getLastModifiedDate().toInstant())\n          .isEqualTo(Instant.EPOCH.plusSeconds(1));\n    }\n  }\n\n  @Test\n  public void testBuild_timestampNonDefault() throws IOException {\n    Path file = createFile(temporaryFolder.getRoot().toPath(), \"fileA\", \"some content\", 54321);\n\n    Blob blob =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(\n                    new FileEntry(\n                        file,\n                        AbsoluteUnixPath.get(\"/fileA\"),\n                        FilePermissions.DEFAULT_FILE_PERMISSIONS,\n                        Instant.ofEpochSecond(123))))\n            .build();\n\n    Path tarFile = temporaryFolder.newFile().toPath();\n    try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) {\n      blob.writeTo(out);\n    }\n\n    // Reads the file back.\n    try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) {\n      assertThat(in.getNextEntry().getLastModifiedDate().toInstant())\n          .isEqualTo(Instant.EPOCH.plusSeconds(123));\n    }\n  }\n\n  @Test\n  public void testBuild_permissions() throws IOException {\n    Path testRoot = temporaryFolder.getRoot().toPath();\n    Path folder = Files.createDirectories(testRoot.resolve(\"files1\"));\n    Path fileA = createFile(testRoot, \"fileA\", \"abc\", 54321);\n    Path fileB = createFile(testRoot, \"fileB\", \"def\", 54321);\n\n    Blob blob =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(\n                    defaultLayerEntry(fileA, AbsoluteUnixPath.get(\"/somewhere/fileA\")),\n                    new FileEntry(\n                        fileB,\n                        AbsoluteUnixPath.get(\"/somewhere/fileB\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        FileEntriesLayer.DEFAULT_MODIFICATION_TIME),\n                    new FileEntry(\n                        folder,\n                        AbsoluteUnixPath.get(\"/somewhere/folder\"),\n                        FilePermissions.fromOctalString(\"456\"),\n                        FileEntriesLayer.DEFAULT_MODIFICATION_TIME)))\n            .build();\n\n    Path tarFile = temporaryFolder.newFile().toPath();\n    try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) {\n      blob.writeTo(out);\n    }\n\n    try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) {\n      // Root folder (default folder permissions)\n      assertThat(in.getNextEntry().getMode()).isEqualTo(040755);\n      // fileA (default file permissions)\n      assertThat(in.getNextEntry().getMode()).isEqualTo(0100644);\n      // fileB (custom file permissions)\n      assertThat(in.getNextEntry().getMode()).isEqualTo(0100123);\n      // folder (custom folder permissions)\n      assertThat(in.getNextEntry().getMode()).isEqualTo(040456);\n    }\n  }\n\n  @Test\n  public void testBuild_ownership() throws IOException {\n    Path testRoot = temporaryFolder.getRoot().toPath();\n    Path someFile = createFile(testRoot, \"someFile\", \"content\", 54321);\n\n    Blob blob =\n        new ReproducibleLayerBuilder(\n                ImmutableList.of(\n                    defaultLayerEntry(someFile, AbsoluteUnixPath.get(\"/file1\")),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file2\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \"\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file3\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \":\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file4\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \"333:\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file5\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \":555\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file6\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \"333:555\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file7\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \"user:\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file8\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \":group\"),\n                    new FileEntry(\n                        someFile,\n                        AbsoluteUnixPath.get(\"/file9\"),\n                        FilePermissions.fromOctalString(\"123\"),\n                        Instant.EPOCH,\n                        \"user:group\")))\n            .build();\n\n    Path tarFile = temporaryFolder.newFile().toPath();\n    try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(tarFile))) {\n      blob.writeTo(out);\n    }\n\n    try (TarArchiveInputStream in = new TarArchiveInputStream(Files.newInputStream(tarFile))) {\n      TarArchiveEntry entry1 = in.getNextEntry();\n      assertThat(entry1.getLongUserId()).isEqualTo(0);\n      assertThat(entry1.getLongGroupId()).isEqualTo(0);\n      assertThat(entry1.getUserName()).isEmpty();\n      assertThat(entry1.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry2 = in.getNextEntry();\n      assertThat(entry2.getLongUserId()).isEqualTo(0);\n      assertThat(entry2.getLongGroupId()).isEqualTo(0);\n      assertThat(entry2.getUserName()).isEmpty();\n      assertThat(entry2.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry3 = in.getNextEntry();\n      assertThat(entry3.getLongUserId()).isEqualTo(0);\n      assertThat(entry3.getLongGroupId()).isEqualTo(0);\n      assertThat(entry3.getUserName()).isEmpty();\n      assertThat(entry3.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry4 = in.getNextEntry();\n      assertThat(entry4.getLongUserId()).isEqualTo(333);\n      assertThat(entry4.getLongGroupId()).isEqualTo(0);\n      assertThat(entry4.getUserName()).isEmpty();\n      assertThat(entry4.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry5 = in.getNextEntry();\n      assertThat(entry5.getLongUserId()).isEqualTo(0);\n      assertThat(entry5.getLongGroupId()).isEqualTo(555);\n      assertThat(entry5.getUserName()).isEmpty();\n      assertThat(entry5.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry6 = in.getNextEntry();\n      assertThat(entry6.getLongUserId()).isEqualTo(333);\n      assertThat(entry6.getLongGroupId()).isEqualTo(555);\n      assertThat(entry6.getUserName()).isEmpty();\n      assertThat(entry6.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry7 = in.getNextEntry();\n      assertThat(entry7.getLongUserId()).isEqualTo(0);\n      assertThat(entry7.getLongGroupId()).isEqualTo(0);\n      assertThat(entry7.getUserName()).isEqualTo(\"user\");\n      assertThat(entry7.getGroupName()).isEmpty();\n\n      TarArchiveEntry entry8 = in.getNextEntry();\n      assertThat(entry8.getLongUserId()).isEqualTo(0);\n      assertThat(entry8.getLongGroupId()).isEqualTo(0);\n      assertThat(entry8.getUserName()).isEmpty();\n      assertThat(entry8.getGroupName()).isEqualTo(\"group\");\n\n      TarArchiveEntry entry9 = in.getNextEntry();\n      assertThat(entry9.getLongUserId()).isEqualTo(0);\n      assertThat(entry9.getLongGroupId()).isEqualTo(0);\n      assertThat(entry9.getUserName()).isEqualTo(\"user\");\n      assertThat(entry9.getGroupName()).isEqualTo(\"group\");\n    }\n  }\n\n  private static Path createFile(Path root, String filename, String content, long modificationTime)\n      throws IOException {\n    Path newFile =\n        Files.write(\n            root.resolve(filename),\n            content.getBytes(StandardCharsets.UTF_8),\n            StandardOpenOption.CREATE_NEW);\n    Files.setLastModifiedTime(newFile, FileTime.fromMillis(modificationTime));\n    return newFile;\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ContainerConfigurationTemplateTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSortedMap;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ContainerConfigurationTemplate}. */\npublic class ContainerConfigurationTemplateTest {\n\n  @Test\n  public void testToJson() throws IOException, URISyntaxException, DigestException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/containerconfig.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Creates the JSON object to serialize.\n    ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate();\n\n    containerConfigJson.setCreated(\"1970-01-01T00:00:20Z\");\n    containerConfigJson.setArchitecture(\"wasm\");\n    containerConfigJson.setOs(\"js\");\n    containerConfigJson.setContainerEnvironment(Arrays.asList(\"VAR1=VAL1\", \"VAR2=VAL2\"));\n    containerConfigJson.setContainerEntrypoint(Arrays.asList(\"some\", \"entrypoint\", \"command\"));\n    containerConfigJson.setContainerCmd(Arrays.asList(\"arg1\", \"arg2\"));\n    containerConfigJson.setContainerHealthCheckTest(Arrays.asList(\"CMD-SHELL\", \"/checkhealth\"));\n    containerConfigJson.setContainerHealthCheckInterval(3000000000L);\n    containerConfigJson.setContainerHealthCheckTimeout(1000000000L);\n    containerConfigJson.setContainerHealthCheckStartPeriod(2000000000L);\n    containerConfigJson.setContainerHealthCheckRetries(3);\n    containerConfigJson.setContainerExposedPorts(\n        ImmutableSortedMap.of(\n            \"1000/tcp\",\n            ImmutableMap.of(),\n            \"2000/tcp\",\n            ImmutableMap.of(),\n            \"3000/udp\",\n            ImmutableMap.of()));\n    containerConfigJson.setContainerLabels(ImmutableMap.of(\"key1\", \"value1\", \"key2\", \"value2\"));\n    containerConfigJson.setContainerVolumes(\n        ImmutableMap.of(\n            \"/var/job-result-data\", ImmutableMap.of(), \"/var/log/my-app-logs\", ImmutableMap.of()));\n    containerConfigJson.setContainerWorkingDir(\"/some/workspace\");\n    containerConfigJson.setContainerUser(\"tomcat\");\n\n    containerConfigJson.addLayerDiffId(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"));\n    containerConfigJson.addHistoryEntry(\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"Bazel\")\n            .setCreatedBy(\"bazel build ...\")\n            .setEmptyLayer(true)\n            .build());\n    containerConfigJson.addHistoryEntry(\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.ofEpochSecond(20))\n            .setAuthor(\"Jib\")\n            .setCreatedBy(\"jib\")\n            .build());\n\n    // Serializes the JSON object.\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(containerConfigJson));\n  }\n\n  @Test\n  public void testFromJson() throws IOException, URISyntaxException, DigestException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/containerconfig.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    ContainerConfigurationTemplate containerConfigJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, ContainerConfigurationTemplate.class);\n\n    Assert.assertEquals(\"1970-01-01T00:00:20Z\", containerConfigJson.getCreated());\n    Assert.assertEquals(\"wasm\", containerConfigJson.getArchitecture());\n    Assert.assertEquals(\"js\", containerConfigJson.getOs());\n    Assert.assertEquals(\n        Arrays.asList(\"VAR1=VAL1\", \"VAR2=VAL2\"), containerConfigJson.getContainerEnvironment());\n    Assert.assertEquals(\n        Arrays.asList(\"some\", \"entrypoint\", \"command\"),\n        containerConfigJson.getContainerEntrypoint());\n    Assert.assertEquals(Arrays.asList(\"arg1\", \"arg2\"), containerConfigJson.getContainerCmd());\n\n    Assert.assertEquals(\n        Arrays.asList(\"CMD-SHELL\", \"/checkhealth\"), containerConfigJson.getContainerHealthTest());\n    Assert.assertNotNull(containerConfigJson.getContainerHealthInterval());\n    Assert.assertEquals(3000000000L, containerConfigJson.getContainerHealthInterval().longValue());\n    Assert.assertNotNull(containerConfigJson.getContainerHealthTimeout());\n    Assert.assertEquals(1000000000L, containerConfigJson.getContainerHealthTimeout().longValue());\n    Assert.assertNotNull(containerConfigJson.getContainerHealthStartPeriod());\n    Assert.assertEquals(\n        2000000000L, containerConfigJson.getContainerHealthStartPeriod().longValue());\n    Assert.assertNotNull(containerConfigJson.getContainerHealthRetries());\n    Assert.assertEquals(3, containerConfigJson.getContainerHealthRetries().intValue());\n\n    Assert.assertEquals(\n        ImmutableMap.of(\"key1\", \"value1\", \"key2\", \"value2\"),\n        containerConfigJson.getContainerLabels());\n    Assert.assertEquals(\"/some/workspace\", containerConfigJson.getContainerWorkingDir());\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        containerConfigJson.getLayerDiffId(0));\n    Assert.assertEquals(\n        ImmutableList.of(\n            HistoryEntry.builder()\n                .setCreationTimestamp(Instant.EPOCH)\n                .setAuthor(\"Bazel\")\n                .setCreatedBy(\"bazel build ...\")\n                .setEmptyLayer(true)\n                .build(),\n            HistoryEntry.builder()\n                .setCreationTimestamp(Instant.ofEpochSecond(20))\n                .setAuthor(\"Jib\")\n                .setCreatedBy(\"jib\")\n                .build()),\n        containerConfigJson.getHistory());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ImageToJsonTranslatorTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.configuration.DockerHealthCheck;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.ImmutableSortedMap;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Map;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ImageToJsonTranslator}. */\npublic class ImageToJsonTranslatorTest {\n\n  private ImageToJsonTranslator imageToJsonTranslator;\n\n  private void setUp(Class<? extends BuildableManifestTemplate> imageFormat)\n      throws DigestException, LayerPropertyNotFoundException {\n    Image.Builder testImageBuilder =\n        Image.builder(imageFormat)\n            .setCreated(Instant.ofEpochSecond(20))\n            .setArchitecture(\"wasm\")\n            .setOs(\"js\")\n            .addEnvironmentVariable(\"VAR1\", \"VAL1\")\n            .addEnvironmentVariable(\"VAR2\", \"VAL2\")\n            .setEntrypoint(Arrays.asList(\"some\", \"entrypoint\", \"command\"))\n            .setProgramArguments(Arrays.asList(\"arg1\", \"arg2\"))\n            .setHealthCheck(\n                DockerHealthCheck.fromCommand(ImmutableList.of(\"CMD-SHELL\", \"/checkhealth\"))\n                    .setInterval(Duration.ofSeconds(3))\n                    .setTimeout(Duration.ofSeconds(1))\n                    .setStartPeriod(Duration.ofSeconds(2))\n                    .setRetries(3)\n                    .build())\n            .addExposedPorts(ImmutableSet.of(Port.tcp(1000), Port.tcp(2000), Port.udp(3000)))\n            .addVolumes(\n                ImmutableSet.of(\n                    AbsoluteUnixPath.get(\"/var/job-result-data\"),\n                    AbsoluteUnixPath.get(\"/var/log/my-app-logs\")))\n            .addLabels(ImmutableMap.of(\"key1\", \"value1\", \"key2\", \"value2\"))\n            .setWorkingDirectory(\"/some/workspace\")\n            .setUser(\"tomcat\");\n\n    DescriptorDigest fakeDigest =\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\");\n    testImageBuilder.addLayer(\n        new Layer() {\n\n          @Override\n          public Blob getBlob() throws LayerPropertyNotFoundException {\n            return Blobs.from(\"ignored\");\n          }\n\n          @Override\n          public BlobDescriptor getBlobDescriptor() throws LayerPropertyNotFoundException {\n            return new BlobDescriptor(1000, fakeDigest);\n          }\n\n          @Override\n          public DescriptorDigest getDiffId() throws LayerPropertyNotFoundException {\n            return fakeDigest;\n          }\n        });\n    testImageBuilder.addHistory(\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.EPOCH)\n            .setAuthor(\"Bazel\")\n            .setCreatedBy(\"bazel build ...\")\n            .setEmptyLayer(true)\n            .build());\n    testImageBuilder.addHistory(\n        HistoryEntry.builder()\n            .setCreationTimestamp(Instant.ofEpochSecond(20))\n            .setAuthor(\"Jib\")\n            .setCreatedBy(\"jib\")\n            .build());\n    imageToJsonTranslator = new ImageToJsonTranslator(testImageBuilder.build());\n  }\n\n  @Test\n  public void testGetContainerConfiguration()\n      throws IOException, URISyntaxException, DigestException {\n    setUp(V22ManifestTemplate.class);\n\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/containerconfig.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Translates the image to the container configuration and writes the JSON string.\n    JsonTemplate containerConfiguration = imageToJsonTranslator.getContainerConfiguration();\n\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(containerConfiguration));\n  }\n\n  @Test\n  public void testGetManifest_v22() throws URISyntaxException, IOException, DigestException {\n    setUp(V22ManifestTemplate.class);\n    testGetManifest(V22ManifestTemplate.class, \"core/json/translated_v22manifest.json\");\n  }\n\n  @Test\n  public void testGetManifest_oci() throws URISyntaxException, IOException, DigestException {\n    setUp(OciManifestTemplate.class);\n    testGetManifest(OciManifestTemplate.class, \"core/json/translated_ocimanifest.json\");\n  }\n\n  @Test\n  public void testPortListToMap() {\n    ImmutableSet<Port> input = ImmutableSet.of(Port.tcp(1000), Port.udp(2000));\n    ImmutableSortedMap<String, Map<?, ?>> expected =\n        ImmutableSortedMap.of(\"1000/tcp\", ImmutableMap.of(), \"2000/udp\", ImmutableMap.of());\n    Assert.assertEquals(expected, ImageToJsonTranslator.portSetToMap(input));\n  }\n\n  @Test\n  public void testVolumeListToMap() {\n    ImmutableSet<AbsoluteUnixPath> input =\n        ImmutableSet.of(\n            AbsoluteUnixPath.get(\"/var/job-result-data\"),\n            AbsoluteUnixPath.get(\"/var/log/my-app-logs\"));\n    ImmutableSortedMap<String, Map<?, ?>> expected =\n        ImmutableSortedMap.of(\n            \"/var/job-result-data\", ImmutableMap.of(), \"/var/log/my-app-logs\", ImmutableMap.of());\n    Assert.assertEquals(expected, ImageToJsonTranslator.volumesSetToMap(input));\n  }\n\n  @Test\n  public void testEnvironmentMapToList() {\n    ImmutableMap<String, String> input = ImmutableMap.of(\"NAME1\", \"VALUE1\", \"NAME2\", \"VALUE2\");\n    ImmutableList<String> expected = ImmutableList.of(\"NAME1=VALUE1\", \"NAME2=VALUE2\");\n    Assert.assertEquals(expected, ImageToJsonTranslator.environmentMapToList(input));\n  }\n\n  /** Tests translation of image to {@link BuildableManifestTemplate}. */\n  private <T extends BuildableManifestTemplate> void testGetManifest(\n      Class<T> manifestTemplateClass, String translatedJsonFilename)\n      throws URISyntaxException, IOException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(translatedJsonFilename).toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Translates the image to the manifest and writes the JSON string.\n    JsonTemplate containerConfiguration = imageToJsonTranslator.getContainerConfiguration();\n    BlobDescriptor blobDescriptor = Digests.computeDigest(containerConfiguration);\n    T manifestTemplate =\n        imageToJsonTranslator.getManifestTemplate(manifestTemplateClass, blobDescriptor);\n\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(manifestTemplate));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/JsonToImageTranslatorTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.Port;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.image.Image;\nimport com.google.cloud.tools.jib.image.Layer;\nimport com.google.cloud.tools.jib.image.LayerCountMismatchException;\nimport com.google.cloud.tools.jib.image.LayerPropertyNotFoundException;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.collect.ImmutableSortedMap;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link JsonToImageTranslator}. */\npublic class JsonToImageTranslatorTest {\n\n  @Test\n  public void testToImage_v21()\n      throws IOException, LayerPropertyNotFoundException, DigestException, URISyntaxException,\n          BadContainerConfigurationFormatException {\n    // Loads the JSON string.\n    Path jsonFile =\n        Paths.get(getClass().getClassLoader().getResource(\"core/json/v21manifest.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    V21ManifestTemplate manifestTemplate =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, V21ManifestTemplate.class);\n\n    Image image = JsonToImageTranslator.toImage(manifestTemplate);\n\n    List<Layer> layers = image.getLayers();\n    Assert.assertEquals(2, layers.size());\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:5bd451067f9ab05e97cda8476c82f86d9b69c2dffb60a8ad2fe3723942544ab3\"),\n        layers.get(0).getBlobDescriptor().getDigest());\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        layers.get(1).getBlobDescriptor().getDigest());\n  }\n\n  @Test\n  public void testToImage_v22()\n      throws IOException, LayerPropertyNotFoundException, LayerCountMismatchException,\n          DigestException, URISyntaxException, BadContainerConfigurationFormatException {\n    testToImage_buildable(\"core/json/v22manifest.json\", V22ManifestTemplate.class);\n  }\n\n  @Test\n  public void testToImage_oci()\n      throws IOException, LayerPropertyNotFoundException, LayerCountMismatchException,\n          DigestException, URISyntaxException, BadContainerConfigurationFormatException {\n    testToImage_buildable(\"core/json/ocimanifest.json\", OciManifestTemplate.class);\n  }\n\n  @Test\n  public void testToImage_canParseTimestampWithOffset()\n      throws IOException, LayerPropertyNotFoundException, URISyntaxException,\n          LayerCountMismatchException, BadContainerConfigurationFormatException {\n    Path containerConfigJson =\n        Paths.get(\n            getClass().getClassLoader().getResource(\"core/json/containerconfig.json\").toURI());\n    ContainerConfigurationTemplate containerConfig =\n        JsonTemplateMapper.readJsonFromFile(\n            containerConfigJson, ContainerConfigurationTemplate.class);\n    containerConfig.setCreated(\"2020-04-21T13:22:10.836777828-07:00\");\n\n    Path manifestJson =\n        Paths.get(getClass().getClassLoader().getResource(\"core/json/v22manifest.json\").toURI());\n    V22ManifestTemplate manifest =\n        JsonTemplateMapper.readJsonFromFile(manifestJson, V22ManifestTemplate.class);\n\n    // Should not throw BadContainerConfigFormatException.\n    // https://github.com/GoogleContainerTools/jib/issues/2428\n    Image image = JsonToImageTranslator.toImage(manifest, containerConfig);\n    Assert.assertEquals(1587500530L, image.getCreated().getEpochSecond());\n  }\n\n  @Test\n  public void testPortMapToList() throws BadContainerConfigurationFormatException {\n    ImmutableSortedMap<String, Map<String, String>> input =\n        ImmutableSortedMap.of(\n            \"1000\",\n            ImmutableMap.of(),\n            \"2000/tcp\",\n            ImmutableMap.of(),\n            \"3000/udp\",\n            ImmutableMap.of());\n    ImmutableSet<Port> expected = ImmutableSet.of(Port.tcp(1000), Port.tcp(2000), Port.udp(3000));\n    Assert.assertEquals(expected, JsonToImageTranslator.portMapToSet(input));\n\n    ImmutableList<Map<String, Map<String, String>>> badInputs =\n        ImmutableList.of(\n            ImmutableMap.of(\"abc\", ImmutableMap.of()),\n            ImmutableMap.of(\"1000-2000\", ImmutableMap.of()),\n            ImmutableMap.of(\"/udp\", ImmutableMap.of()),\n            ImmutableMap.of(\"123/xxx\", ImmutableMap.of()));\n    for (Map<String, Map<String, String>> badInput : badInputs) {\n      try {\n        JsonToImageTranslator.portMapToSet(badInput);\n        Assert.fail();\n      } catch (BadContainerConfigurationFormatException ignored) {\n        // ignored\n      }\n    }\n  }\n\n  @Test\n  public void testVolumeMapToList() throws BadContainerConfigurationFormatException {\n    ImmutableSortedMap<String, Map<String, String>> input =\n        ImmutableSortedMap.of(\n            \"/var/job-result-data\", ImmutableMap.of(), \"/var/log/my-app-logs\", ImmutableMap.of());\n    ImmutableSet<AbsoluteUnixPath> expected =\n        ImmutableSet.of(\n            AbsoluteUnixPath.get(\"/var/job-result-data\"),\n            AbsoluteUnixPath.get(\"/var/log/my-app-logs\"));\n    Assert.assertEquals(expected, JsonToImageTranslator.volumeMapToSet(input));\n\n    ImmutableList<Map<String, Map<String, String>>> badInputs =\n        ImmutableList.of(\n            ImmutableMap.of(\"var/job-result-data\", ImmutableMap.of()),\n            ImmutableMap.of(\"log\", ImmutableMap.of()),\n            ImmutableMap.of(\"C:/udp\", ImmutableMap.of()));\n    for (Map<String, Map<String, String>> badInput : badInputs) {\n      try {\n        JsonToImageTranslator.volumeMapToSet(badInput);\n        Assert.fail();\n      } catch (BadContainerConfigurationFormatException ignored) {\n        // ignored\n      }\n    }\n  }\n\n  @Test\n  public void testJsonToImageTranslatorRegex() {\n    assertGoodEnvironmentPattern(\"NAME=VALUE\", \"NAME\", \"VALUE\");\n    assertGoodEnvironmentPattern(\"A1203921=www=ww\", \"A1203921\", \"www=ww\");\n    assertGoodEnvironmentPattern(\"&*%(&#$(*@(%&@$*$(=\", \"&*%(&#$(*@(%&@$*$(\", \"\");\n    assertGoodEnvironmentPattern(\"m_a_8943=100\", \"m_a_8943\", \"100\");\n    assertGoodEnvironmentPattern(\"A_B_C_D=*****\", \"A_B_C_D\", \"*****\");\n\n    assertBadEnvironmentPattern(\"=================\");\n    assertBadEnvironmentPattern(\"A_B_C\");\n  }\n\n  private void assertGoodEnvironmentPattern(\n      String input, String expectedName, String expectedValue) {\n    Matcher matcher = JsonToImageTranslator.ENVIRONMENT_PATTERN.matcher(input);\n    Assert.assertTrue(matcher.matches());\n    Assert.assertEquals(expectedName, matcher.group(\"name\"));\n    Assert.assertEquals(expectedValue, matcher.group(\"value\"));\n  }\n\n  private void assertBadEnvironmentPattern(String input) {\n    Matcher matcher = JsonToImageTranslator.ENVIRONMENT_PATTERN.matcher(input);\n    Assert.assertFalse(matcher.matches());\n  }\n\n  private <T extends BuildableManifestTemplate> void testToImage_buildable(\n      String jsonFilename, Class<T> manifestTemplateClass)\n      throws IOException, LayerPropertyNotFoundException, LayerCountMismatchException,\n          DigestException, URISyntaxException, BadContainerConfigurationFormatException {\n    // Loads the container configuration JSON.\n    Path containerConfigurationJsonFile =\n        Paths.get(\n            getClass().getClassLoader().getResource(\"core/json/containerconfig.json\").toURI());\n    ContainerConfigurationTemplate containerConfigurationTemplate =\n        JsonTemplateMapper.readJsonFromFile(\n            containerConfigurationJsonFile, ContainerConfigurationTemplate.class);\n\n    // Loads the manifest JSON.\n    Path manifestJsonFile =\n        Paths.get(getClass().getClassLoader().getResource(jsonFilename).toURI());\n    T manifestTemplate =\n        JsonTemplateMapper.readJsonFromFile(manifestJsonFile, manifestTemplateClass);\n\n    Image image = JsonToImageTranslator.toImage(manifestTemplate, containerConfigurationTemplate);\n\n    List<Layer> layers = image.getLayers();\n    Assert.assertEquals(1, layers.size());\n    Assert.assertEquals(\n        new BlobDescriptor(\n            1000000,\n            DescriptorDigest.fromDigest(\n                \"sha256:4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\")),\n        layers.get(0).getBlobDescriptor());\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        layers.get(0).getDiffId());\n    Assert.assertEquals(\n        ImmutableList.of(\n            HistoryEntry.builder()\n                .setCreationTimestamp(Instant.EPOCH)\n                .setAuthor(\"Bazel\")\n                .setCreatedBy(\"bazel build ...\")\n                .setEmptyLayer(true)\n                .build(),\n            HistoryEntry.builder()\n                .setCreationTimestamp(Instant.ofEpochSecond(20))\n                .setAuthor(\"Jib\")\n                .setCreatedBy(\"jib\")\n                .build()),\n        image.getHistory());\n    Assert.assertEquals(Instant.ofEpochSecond(20), image.getCreated());\n    Assert.assertEquals(Arrays.asList(\"some\", \"entrypoint\", \"command\"), image.getEntrypoint());\n    Assert.assertEquals(ImmutableMap.of(\"VAR1\", \"VAL1\", \"VAR2\", \"VAL2\"), image.getEnvironment());\n    Assert.assertEquals(\"/some/workspace\", image.getWorkingDirectory());\n    Assert.assertEquals(\n        ImmutableSet.of(Port.tcp(1000), Port.tcp(2000), Port.udp(3000)), image.getExposedPorts());\n    Assert.assertEquals(\n        ImmutableSet.of(\n            AbsoluteUnixPath.get(\"/var/job-result-data\"),\n            AbsoluteUnixPath.get(\"/var/log/my-app-logs\")),\n        image.getVolumes());\n    Assert.assertEquals(\"tomcat\", image.getUser());\n    Assert.assertEquals(\"value1\", image.getLabels().get(\"key1\"));\n    Assert.assertEquals(\"value2\", image.getLabels().get(\"key2\"));\n    Assert.assertEquals(2, image.getLabels().size());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ManifestListGeneratorTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.image.Image;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/** Tests for {@link ManifestListGenerator}. */\npublic class ManifestListGeneratorTest {\n\n  private Image image1;\n  private Image image2;\n  private ManifestListGenerator manifestListGenerator;\n\n  @Before\n  public void setUp() {\n    image1 =\n        Image.builder(V22ManifestTemplate.class).setArchitecture(\"amd64\").setOs(\"linux\").build();\n    image2 =\n        Image.builder(V22ManifestTemplate.class).setArchitecture(\"arm64\").setOs(\"windows\").build();\n    manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2));\n  }\n\n  @Test\n  public void testGetManifestListTemplate() throws IOException {\n\n    // Expected Manifest List JSON\n    //  {\n    //  \"schemaVersion\":2,\n    //  \"mediaType\":\"application/vnd.docker.distribution.manifest.list.v2+json\",\n    //  \"manifests\":[\n    //    {\n    //      \"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n    //      \"digest\":\"sha256:1f25787aab4669d252bdae09a72b9c345d2a7b8c64c8dbfba4c82af4834dbccc\",\n    //      \"size\":264,\n    //      \"platform\":{\n    //        \"architecture\":\"amd64\",\n    //        \"os\":\"linux\"\n    //      }\n    //    },\n    //    {\n    //      \"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\n    //      \"digest\":\"sha256:51038a7a91c0e8f747e05dd84c3b0393a7016ec312ce384fc945356778497ae3\",\n    //      \"size\":264,\n    //      \"platform\":{\n    //        \"architecture\":\"arm64\",\n    //        \"os\":\"windows\"\n    //      }\n    //    }\n    //   ]\n    // }\n\n    ManifestTemplate manifestTemplate =\n        manifestListGenerator.getManifestListTemplate(V22ManifestTemplate.class);\n    Assert.assertTrue(manifestTemplate instanceof V22ManifestListTemplate);\n    V22ManifestListTemplate manifestList = (V22ManifestListTemplate) manifestTemplate;\n    Assert.assertEquals(2, manifestList.getSchemaVersion());\n    Assert.assertEquals(\n        Arrays.asList(\"sha256:1f25787aab4669d252bdae09a72b9c345d2a7b8c64c8dbfba4c82af4834dbccc\"),\n        manifestList.getDigestsForPlatform(\"amd64\", \"linux\"));\n    Assert.assertEquals(\n        Arrays.asList(\"sha256:51038a7a91c0e8f747e05dd84c3b0393a7016ec312ce384fc945356778497ae3\"),\n        manifestList.getDigestsForPlatform(\"arm64\", \"windows\"));\n  }\n\n  @Test\n  public void testGetManifestListTemplate_emptyImagesList() throws IOException {\n    try {\n      new ManifestListGenerator(Collections.emptyList())\n          .getManifestListTemplate(V22ManifestTemplate.class);\n      Assert.fail();\n    } catch (IllegalStateException ex) {\n      Assert.assertEquals(\"no images given\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testGetManifestListTemplate_unsupportedImageFormat() throws IOException {\n    try {\n      new ManifestListGenerator(Arrays.asList(image1, image2))\n          .getManifestListTemplate(OciManifestTemplate.class);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\"Build an OCI image index is not yet supported\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/OciIndexTemplateTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link OciIndexTemplate}. */\npublic class OciIndexTemplateTest {\n\n  @Test\n  public void testToJson() throws DigestException, IOException, URISyntaxException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/ociindex.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Creates the JSON object to serialize.\n    OciIndexTemplate ociIndexJson = new OciIndexTemplate();\n    ociIndexJson.addManifest(\n        new BlobDescriptor(\n            1000,\n            DescriptorDigest.fromDigest(\n                \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\")),\n        \"regis.try/repo:tag\");\n\n    // Serializes the JSON object.\n    Assert.assertEquals(\n        expectedJson.replaceAll(\"[\\r\\n\\t ]\", \"\"), JsonTemplateMapper.toUtf8String(ociIndexJson));\n  }\n\n  @Test\n  public void testFromJson() throws IOException, URISyntaxException, DigestException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/ociindex.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    OciIndexTemplate ociIndexJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, OciIndexTemplate.class);\n    BuildableManifestTemplate.ContentDescriptorTemplate manifest =\n        ociIndexJson.getManifests().get(0);\n\n    Assert.assertEquals(2, ociIndexJson.getSchemaVersion());\n    Assert.assertEquals(OciIndexTemplate.MEDIA_TYPE, ociIndexJson.getManifestMediaType());\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        manifest.getDigest());\n    Assert.assertEquals(\n        \"regis.try/repo:tag\", manifest.getAnnotations().get(\"org.opencontainers.image.ref.name\"));\n    Assert.assertEquals(1000, manifest.getSize());\n  }\n\n  @Test\n  public void testToJsonWithPlatform() throws DigestException, IOException, URISyntaxException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/ociindex_platforms.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Creates the JSON object to serialize.\n    OciIndexTemplate ociIndexJson = new OciIndexTemplate();\n\n    OciIndexTemplate.ManifestDescriptorTemplate ppc64leManifest =\n        new OciIndexTemplate.ManifestDescriptorTemplate(\n            OciManifestTemplate.MANIFEST_MEDIA_TYPE,\n            7143,\n            DescriptorDigest.fromDigest(\n                \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\"));\n    ppc64leManifest.setPlatform(\"ppc64le\", \"linux\");\n    ociIndexJson.addManifest(ppc64leManifest);\n\n    OciIndexTemplate.ManifestDescriptorTemplate amd64Manifest =\n        new OciIndexTemplate.ManifestDescriptorTemplate(\n            OciManifestTemplate.MANIFEST_MEDIA_TYPE,\n            7682,\n            DescriptorDigest.fromDigest(\n                \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\"));\n    amd64Manifest.setPlatform(\"amd64\", \"linux\");\n    ociIndexJson.addManifest(amd64Manifest);\n\n    // Serializes the JSON object.\n    Assert.assertEquals(\n        expectedJson.replaceAll(\"[\\r\\n\\t ]\", \"\"), JsonTemplateMapper.toUtf8String(ociIndexJson));\n  }\n\n  @Test\n  public void testFromJsonWithPlatform() throws IOException, URISyntaxException, DigestException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/ociindex_platforms.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    OciIndexTemplate ociIndexJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, OciIndexTemplate.class);\n\n    Assert.assertEquals(2, ociIndexJson.getManifests().size());\n    Assert.assertEquals(\n        \"ppc64le\", ociIndexJson.getManifests().get(0).getPlatform().getArchitecture());\n    Assert.assertEquals(\"linux\", ociIndexJson.getManifests().get(0).getPlatform().getOs());\n    Assert.assertEquals(\n        \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n        ociIndexJson.getDigestsForPlatform(\"ppc64le\", \"linux\").get(0));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/OciManifestTemplateTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link OciManifestTemplate}. */\npublic class OciManifestTemplateTest {\n\n  @Test\n  public void testToJson() throws DigestException, IOException, URISyntaxException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/ocimanifest.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Creates the JSON object to serialize.\n    OciManifestTemplate manifestJson = new OciManifestTemplate();\n\n    manifestJson.setContainerConfiguration(\n        1000,\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"));\n\n    manifestJson.addLayer(\n        1000_000,\n        DescriptorDigest.fromHash(\n            \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"));\n\n    // Serializes the JSON object.\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(manifestJson));\n  }\n\n  @Test\n  public void testFromJson() throws IOException, URISyntaxException, DigestException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/ocimanifest.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    OciManifestTemplate manifestJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, OciManifestTemplate.class);\n\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        manifestJson.getContainerConfiguration().getDigest());\n\n    Assert.assertEquals(1000, manifestJson.getContainerConfiguration().getSize());\n\n    Assert.assertEquals(\n        DescriptorDigest.fromHash(\n            \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"),\n        manifestJson.getLayers().get(0).getDigest());\n\n    Assert.assertEquals(1000_000, manifestJson.getLayers().get(0).getSize());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/V21ManifestTemplateTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link V21ManifestTemplate}. */\npublic class V21ManifestTemplateTest {\n\n  @Test\n  public void testFromJson() throws URISyntaxException, IOException, DigestException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/v21manifest.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    V21ManifestTemplate manifestJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, V21ManifestTemplate.class);\n\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        manifestJson.getFsLayers().get(0).getDigest());\n\n    ContainerConfigurationTemplate containerConfiguration =\n        manifestJson.getContainerConfiguration().orElse(null);\n    Assert.assertEquals(\n        Arrays.asList(\"JAVA_HOME=/opt/openjdk\", \"PATH=/opt/openjdk/bin\"),\n        containerConfiguration.getContainerEnvironment());\n    Assert.assertEquals(\n        Arrays.asList(\"/opt/openjdk/bin/java\"), containerConfiguration.getContainerEntrypoint());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/V22ManifestListTemplateTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class V22ManifestListTemplateTest {\n\n  @Test\n  public void testFromJson() throws IOException, URISyntaxException {\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/v22manifest_list.json\").toURI());\n\n    V22ManifestListTemplate manifestListJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, V22ManifestListTemplate.class);\n\n    Assert.assertEquals(2, manifestListJson.getSchemaVersion());\n    List<ManifestDescriptorTemplate> manifests = manifestListJson.getManifests();\n    Assert.assertEquals(3, manifests.size());\n\n    List<String> validPlatformPpc = manifestListJson.getDigestsForPlatform(\"ppc64le\", \"linux\");\n    Assert.assertEquals(1, validPlatformPpc.size());\n    Assert.assertEquals(\n        \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n        validPlatformPpc.get(0));\n\n    List<String> validPlatformAmd = manifestListJson.getDigestsForPlatform(\"amd64\", \"linux\");\n    Assert.assertEquals(2, validPlatformAmd.size());\n    Assert.assertEquals(\n        \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n        validPlatformAmd.get(0));\n    Assert.assertEquals(\n        \"sha256:cccbcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501999\",\n        validPlatformAmd.get(1));\n\n    List<String> invalidArch = manifestListJson.getDigestsForPlatform(\"amd72\", \"linux\");\n    Assert.assertEquals(0, invalidArch.size());\n\n    List<String> invalidOs = manifestListJson.getDigestsForPlatform(\"amd64\", \"minix\");\n    Assert.assertEquals(0, invalidOs.size());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/image/json/V22ManifestTemplateTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.image.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.image.json.BuildableManifestTemplate.ContentDescriptorTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link V22ManifestTemplate}. */\npublic class V22ManifestTemplateTest {\n\n  @Test\n  public void testToJson() throws DigestException, IOException, URISyntaxException {\n    // Loads the expected JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/v22manifest.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    // Creates the JSON object to serialize.\n    V22ManifestTemplate manifestJson = new V22ManifestTemplate();\n\n    manifestJson.setContainerConfiguration(\n        1000,\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"));\n\n    manifestJson.addLayer(\n        1000_000,\n        DescriptorDigest.fromHash(\n            \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"));\n\n    // Serializes the JSON object.\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(manifestJson));\n  }\n\n  @Test\n  public void testFromJson() throws IOException, URISyntaxException, DigestException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/v22manifest.json\").toURI());\n\n    // Deserializes into a manifest JSON object.\n    V22ManifestTemplate manifestJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, V22ManifestTemplate.class);\n\n    Assert.assertEquals(\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"),\n        manifestJson.getContainerConfiguration().getDigest());\n\n    Assert.assertEquals(1000, manifestJson.getContainerConfiguration().getSize());\n\n    Assert.assertEquals(\n        DescriptorDigest.fromHash(\n            \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"),\n        manifestJson.getLayers().get(0).getDigest());\n\n    Assert.assertEquals(1000_000, manifestJson.getLayers().get(0).getSize());\n  }\n\n  @Test\n  public void testFromJson_optionalProperties() throws IOException, URISyntaxException {\n    Path jsonFile =\n        Paths.get(Resources.getResource(\"core/json/v22manifest_optional_properties.json\").toURI());\n\n    V22ManifestTemplate manifestJson =\n        JsonTemplateMapper.readJsonFromFile(jsonFile, V22ManifestTemplate.class);\n\n    List<ContentDescriptorTemplate> layers = manifestJson.getLayers();\n    Assert.assertEquals(4, layers.size());\n    Assert.assertNull(layers.get(0).getUrls());\n    Assert.assertNull(layers.get(0).getAnnotations());\n\n    Assert.assertEquals(Arrays.asList(\"url-foo\", \"url-bar\"), layers.get(1).getUrls());\n    Assert.assertNull(layers.get(1).getAnnotations());\n\n    Assert.assertNull(layers.get(2).getUrls());\n    Assert.assertEquals(ImmutableMap.of(\"key-foo\", \"value-foo\"), layers.get(2).getAnnotations());\n\n    Assert.assertEquals(Arrays.asList(\"cool-url\"), layers.get(3).getUrls());\n    Assert.assertEquals(\n        ImmutableMap.of(\"key1\", \"value1\", \"key2\", \"value2\"), layers.get(3).getAnnotations());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/json/JsonTemplateMapperTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.json;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link JsonTemplateMapper}. */\npublic class JsonTemplateMapperTest {\n\n  private static class TestJson implements JsonTemplate {\n    private int number;\n    private String text;\n    private DescriptorDigest digest;\n    private InnerObject innerObject;\n    private List<InnerObject> list;\n\n    private static class InnerObject implements JsonTemplate {\n      // This field has the same name as a field in the outer class, but either NOT interfere with\n      // the other.\n      private int number;\n      private List<String> texts;\n      private List<DescriptorDigest> digests;\n    }\n  }\n\n  @Test\n  public void testWriteJson() throws DigestException, IOException, URISyntaxException {\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/basic.json\").toURI());\n    String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n\n    TestJson testJson = new TestJson();\n    testJson.number = 54;\n    testJson.text = \"crepecake\";\n    testJson.digest =\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\");\n    testJson.innerObject = new TestJson.InnerObject();\n    testJson.innerObject.number = 23;\n    testJson.innerObject.texts = Arrays.asList(\"first text\", \"second text\");\n    testJson.innerObject.digests =\n        Arrays.asList(\n            DescriptorDigest.fromDigest(\n                \"sha256:91e0cae00b86c289b33fee303a807ae72dd9f0315c16b74e6ab0cdbe9d996c10\"),\n            DescriptorDigest.fromHash(\n                \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"));\n\n    TestJson.InnerObject innerObject1 = new TestJson.InnerObject();\n    innerObject1.number = 42;\n    innerObject1.texts = Collections.emptyList();\n    TestJson.InnerObject innerObject2 = new TestJson.InnerObject();\n    innerObject2.number = 99;\n    innerObject2.texts = Collections.singletonList(\"some text\");\n    innerObject2.digests =\n        Collections.singletonList(\n            DescriptorDigest.fromDigest(\n                \"sha256:d38f571aa1c11e3d516e0ef7e513e7308ccbeb869770cb8c4319d63b10a0075e\"));\n    testJson.list = Arrays.asList(innerObject1, innerObject2);\n\n    Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(testJson));\n  }\n\n  @Test\n  public void testReadJsonWithLock() throws IOException, URISyntaxException, DigestException {\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/basic.json\").toURI());\n\n    // Deserializes into a metadata JSON object.\n    TestJson testJson = JsonTemplateMapper.readJsonFromFileWithLock(jsonFile, TestJson.class);\n\n    MatcherAssert.assertThat(testJson.number, CoreMatchers.is(54));\n    MatcherAssert.assertThat(testJson.text, CoreMatchers.is(\"crepecake\"));\n    MatcherAssert.assertThat(\n        testJson.digest,\n        CoreMatchers.is(\n            DescriptorDigest.fromDigest(\n                \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\")));\n    MatcherAssert.assertThat(\n        testJson.innerObject, CoreMatchers.instanceOf(TestJson.InnerObject.class));\n    MatcherAssert.assertThat(testJson.innerObject.number, CoreMatchers.is(23));\n    MatcherAssert.assertThat(\n        testJson.innerObject.texts, CoreMatchers.is(Arrays.asList(\"first text\", \"second text\")));\n    MatcherAssert.assertThat(\n        testJson.innerObject.digests,\n        CoreMatchers.is(\n            Arrays.asList(\n                DescriptorDigest.fromDigest(\n                    \"sha256:91e0cae00b86c289b33fee303a807ae72dd9f0315c16b74e6ab0cdbe9d996c10\"),\n                DescriptorDigest.fromHash(\n                    \"4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"))));\n    // ignore testJson.list\n  }\n\n  @Test\n  public void testReadListOfJson() throws IOException, URISyntaxException, DigestException {\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/basic_list.json\").toURI());\n\n    String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n    List<TestJson> listofJsons = JsonTemplateMapper.readListOfJson(jsonString, TestJson.class);\n    TestJson json1 = listofJsons.get(0);\n    TestJson json2 = listofJsons.get(1);\n\n    DescriptorDigest digest1 =\n        DescriptorDigest.fromDigest(\n            \"sha256:91e0cae00b86c289b33fee303a807ae72dd9f0315c16b74e6ab0cdbe9d996c10\");\n    DescriptorDigest digest2 =\n        DescriptorDigest.fromDigest(\n            \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\");\n\n    Assert.assertEquals(1, json1.number);\n    Assert.assertEquals(2, json2.number);\n    Assert.assertEquals(\"text1\", json1.text);\n    Assert.assertEquals(\"text2\", json2.text);\n    Assert.assertEquals(digest1, json1.digest);\n    Assert.assertEquals(digest2, json2.digest);\n    Assert.assertEquals(10, json1.innerObject.number);\n    Assert.assertEquals(20, json2.innerObject.number);\n    Assert.assertEquals(2, json1.list.size());\n    Assert.assertTrue(json2.list.isEmpty());\n  }\n\n  @Test\n  public void testToBlob_listOfJson() throws IOException, URISyntaxException {\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/basic_list.json\").toURI());\n\n    String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);\n    List<TestJson> listOfJson = JsonTemplateMapper.readListOfJson(jsonString, TestJson.class);\n\n    Assert.assertEquals(jsonString, JsonTemplateMapper.toUtf8String(listOfJson));\n  }\n\n  @Test\n  public void testReadJson_inputStream() throws IOException {\n    String testJson = \"{\\\"number\\\":3, \\\"text\\\":\\\"cool\\\"}\";\n    ByteArrayInputStream in = new ByteArrayInputStream(testJson.getBytes(StandardCharsets.UTF_8));\n    TestJson json = JsonTemplateMapper.readJson(in, TestJson.class);\n    Assert.assertEquals(3, json.number);\n    Assert.assertEquals(\"cool\", json.text);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpHeaders;\nimport com.google.api.client.http.HttpMethods;\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Collections;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link AuthenticationMethodRetriever}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class AuthenticationMethodRetrieverTest {\n\n  @Mock private ResponseException mockResponseException;\n  @Mock private HttpHeaders mockHeaders;\n  @Mock private FailoverHttpClient httpClient;\n\n  private final RegistryEndpointRequestProperties fakeRegistryEndpointRequestProperties =\n      new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\");\n  private final AuthenticationMethodRetriever testAuthenticationMethodRetriever =\n      new AuthenticationMethodRetriever(\n          fakeRegistryEndpointRequestProperties, \"user-agent\", httpClient);\n\n  @Test\n  public void testGetContent() {\n    Assert.assertNull(testAuthenticationMethodRetriever.getContent());\n  }\n\n  @Test\n  public void testGetAccept() {\n    Assert.assertEquals(0, testAuthenticationMethodRetriever.getAccept().size());\n  }\n\n  @Test\n  public void testHandleResponse() {\n    Assert.assertFalse(\n        testAuthenticationMethodRetriever.handleResponse(Mockito.mock(Response.class)).isPresent());\n  }\n\n  @Test\n  public void testGetApiRoute() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"http://someApiBase/\"),\n        testAuthenticationMethodRetriever.getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testGetHttpMethod() {\n    Assert.assertEquals(HttpMethods.GET, testAuthenticationMethodRetriever.getHttpMethod());\n  }\n\n  @Test\n  public void testGetActionDescription() {\n    Assert.assertEquals(\n        \"retrieve authentication method for someServerUrl\",\n        testAuthenticationMethodRetriever.getActionDescription());\n  }\n\n  @Test\n  public void testHandleHttpResponseException_invalidStatusCode() throws RegistryErrorException {\n    Mockito.when(mockResponseException.getStatusCode()).thenReturn(-1);\n\n    try {\n      testAuthenticationMethodRetriever.handleHttpResponseException(mockResponseException);\n      Assert.fail(\n          \"Authentication method retriever should only handle HTTP 401 Unauthorized errors\");\n\n    } catch (ResponseException ex) {\n      Assert.assertEquals(mockResponseException, ex);\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException_noHeader() throws ResponseException {\n    Mockito.when(mockResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);\n    Mockito.when(mockResponseException.getHeaders()).thenReturn(mockHeaders);\n    Mockito.when(mockHeaders.getAuthenticate()).thenReturn(null);\n\n    try {\n      testAuthenticationMethodRetriever.handleHttpResponseException(mockResponseException);\n      Assert.fail(\n          \"Authentication method retriever should fail if 'WWW-Authenticate' header is not found\");\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"'WWW-Authenticate' header not found\"));\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException_badAuthenticationMethod() throws ResponseException {\n    String authenticationMethod = \"bad authentication method\";\n\n    Mockito.when(mockResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);\n    Mockito.when(mockResponseException.getHeaders()).thenReturn(mockHeaders);\n    Mockito.when(mockHeaders.getAuthenticate()).thenReturn(authenticationMethod);\n\n    try {\n      testAuthenticationMethodRetriever.handleHttpResponseException(mockResponseException);\n      Assert.fail(\n          \"Authentication method retriever should fail if 'WWW-Authenticate' header failed to parse\");\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Failed get authentication method from 'WWW-Authenticate' header\"));\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException_pass()\n      throws RegistryErrorException, ResponseException, MalformedURLException {\n    String authenticationMethod =\n        \"Bearer realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\";\n\n    Mockito.when(mockResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);\n    Mockito.when(mockResponseException.getHeaders()).thenReturn(mockHeaders);\n    Mockito.when(mockHeaders.getAuthenticate()).thenReturn(authenticationMethod);\n\n    RegistryAuthenticator registryAuthenticator =\n        testAuthenticationMethodRetriever.handleHttpResponseException(mockResponseException).get();\n\n    Assert.assertEquals(\n        new URL(\"https://somerealm?service=someservice&scope=repository:someImageName:someScope\"),\n        registryAuthenticator.getAuthenticationUrl(\n            null, Collections.singletonMap(\"someImageName\", \"someScope\")));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobCheckerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;\nimport com.google.cloud.tools.jib.registry.json.ErrorResponseTemplate;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.security.DigestException;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link BlobChecker}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class BlobCheckerTest {\n\n  @Mock private Response mockResponse;\n\n  private final RegistryEndpointRequestProperties fakeRegistryEndpointRequestProperties =\n      new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\");\n\n  private BlobChecker testBlobChecker;\n  private DescriptorDigest fakeDigest;\n\n  @Before\n  public void setUpFakes() throws DigestException {\n    fakeDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    testBlobChecker = new BlobChecker(fakeRegistryEndpointRequestProperties, fakeDigest);\n  }\n\n  @Test\n  public void testHandleResponse() throws RegistryErrorException {\n    Mockito.when(mockResponse.getContentLength()).thenReturn(0L);\n    BlobDescriptor expectedBlobDescriptor = new BlobDescriptor(0, fakeDigest);\n\n    BlobDescriptor blobDescriptor = testBlobChecker.handleResponse(mockResponse).get();\n\n    Assert.assertEquals(expectedBlobDescriptor, blobDescriptor);\n  }\n\n  @Test\n  public void testHandleResponse_noContentLength() {\n    Mockito.when(mockResponse.getContentLength()).thenReturn(-1L);\n\n    try {\n      testBlobChecker.handleResponse(mockResponse);\n      Assert.fail(\"Should throw exception if Content-Length header is not present\");\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"Did not receive Content-Length header\"));\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException() throws IOException {\n    ResponseException mockResponseException = Mockito.mock(ResponseException.class);\n    Mockito.when(mockResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_NOT_FOUND);\n\n    ErrorResponseTemplate emptyErrorResponseTemplate =\n        new ErrorResponseTemplate()\n            .addError(new ErrorEntryTemplate(ErrorCodes.BLOB_UNKNOWN.name(), \"some message\"));\n    Mockito.when(mockResponseException.getContent())\n        .thenReturn(JsonTemplateMapper.toUtf8String(emptyErrorResponseTemplate));\n\n    Assert.assertFalse(\n        testBlobChecker.handleHttpResponseException(mockResponseException).isPresent());\n  }\n\n  @Test\n  public void testHandleHttpResponseException_hasOtherErrors() throws IOException {\n    ResponseException mockResponseException = Mockito.mock(ResponseException.class);\n    Mockito.when(mockResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_NOT_FOUND);\n\n    ErrorResponseTemplate emptyErrorResponseTemplate =\n        new ErrorResponseTemplate()\n            .addError(new ErrorEntryTemplate(ErrorCodes.BLOB_UNKNOWN.name(), \"some message\"))\n            .addError(new ErrorEntryTemplate(ErrorCodes.MANIFEST_UNKNOWN.name(), \"some message\"));\n    Mockito.when(mockResponseException.getContent())\n        .thenReturn(JsonTemplateMapper.toUtf8String(emptyErrorResponseTemplate));\n\n    try {\n      testBlobChecker.handleHttpResponseException(mockResponseException);\n      Assert.fail(\"Non-BLOB_UNKNOWN errors should not be handled\");\n\n    } catch (ResponseException ex) {\n      Assert.assertEquals(mockResponseException, ex);\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException_notBlobUnknown() throws IOException {\n    ResponseException mockResponseException = Mockito.mock(ResponseException.class);\n    Mockito.when(mockResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_NOT_FOUND);\n\n    ErrorResponseTemplate emptyErrorResponseTemplate = new ErrorResponseTemplate();\n    Mockito.when(mockResponseException.getContent())\n        .thenReturn(JsonTemplateMapper.toUtf8String(emptyErrorResponseTemplate));\n\n    try {\n      testBlobChecker.handleHttpResponseException(mockResponseException);\n      Assert.fail(\"Non-BLOB_UNKNOWN errors should not be handled\");\n\n    } catch (ResponseException ex) {\n      Assert.assertEquals(mockResponseException, ex);\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException_invalidStatusCode() {\n    ResponseException mockResponseException = Mockito.mock(ResponseException.class);\n    Mockito.when(mockResponseException.getStatusCode()).thenReturn(-1);\n\n    try {\n      testBlobChecker.handleHttpResponseException(mockResponseException);\n      Assert.fail(\"Non-404 status codes should not be handled\");\n\n    } catch (ResponseException ex) {\n      Assert.assertEquals(mockResponseException, ex);\n    }\n  }\n\n  @Test\n  public void testGetApiRoute() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"http://someApiBase/someImageName/blobs/\" + fakeDigest),\n        testBlobChecker.getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testGetContent() {\n    Assert.assertNull(testBlobChecker.getContent());\n  }\n\n  @Test\n  public void testGetAccept() {\n    Assert.assertEquals(0, testBlobChecker.getAccept().size());\n  }\n\n  @Test\n  public void testGetActionDescription() {\n    Assert.assertEquals(\n        \"check BLOB exists for someServerUrl/someImageName with digest \" + fakeDigest,\n        testBlobChecker.getActionDescription());\n  }\n\n  @Test\n  public void testGetHttpMethod() {\n    Assert.assertEquals(\"HEAD\", testBlobChecker.getHttpMethod());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobPullerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.hash.CountingDigestOutputStream;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.Response;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.security.DigestException;\nimport java.util.concurrent.atomic.LongAdder;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link BlobPuller}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class BlobPullerTest {\n\n  private final RegistryEndpointRequestProperties fakeRegistryEndpointRequestProperties =\n      new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\");\n  private DescriptorDigest fakeDigest;\n\n  private final ByteArrayOutputStream layerContentOutputStream = new ByteArrayOutputStream();\n  private final CountingDigestOutputStream layerOutputStream =\n      new CountingDigestOutputStream(layerContentOutputStream);\n\n  private BlobPuller testBlobPuller;\n\n  @Before\n  public void setUpFakes() throws DigestException {\n    fakeDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n\n    testBlobPuller =\n        new BlobPuller(\n            fakeRegistryEndpointRequestProperties,\n            fakeDigest,\n            layerOutputStream,\n            ignored -> {},\n            ignored -> {});\n  }\n\n  @Test\n  public void testHandleResponse() throws IOException, UnexpectedBlobDigestException {\n    InputStream blobContent =\n        new ByteArrayInputStream(\"some BLOB content\".getBytes(StandardCharsets.UTF_8));\n    DescriptorDigest testBlobDigest = Digests.computeDigest(blobContent).getDigest();\n    blobContent.reset();\n\n    Response mockResponse = Mockito.mock(Response.class);\n    Mockito.when(mockResponse.getContentLength()).thenReturn((long) \"some BLOB content\".length());\n    Mockito.when(mockResponse.getBody()).thenReturn(blobContent);\n\n    LongAdder byteCount = new LongAdder();\n    BlobPuller blobPuller =\n        new BlobPuller(\n            fakeRegistryEndpointRequestProperties,\n            testBlobDigest,\n            layerOutputStream,\n            size -> Assert.assertEquals(\"some BLOB content\".length(), size.longValue()),\n            byteCount::add);\n    blobPuller.handleResponse(mockResponse);\n    Assert.assertEquals(\n        \"some BLOB content\",\n        new String(layerContentOutputStream.toByteArray(), StandardCharsets.UTF_8));\n    Assert.assertEquals(testBlobDigest, layerOutputStream.computeDigest().getDigest());\n    Assert.assertEquals(\"some BLOB content\".length(), byteCount.sum());\n  }\n\n  @Test\n  public void testHandleResponse_unexpectedDigest() throws IOException {\n    InputStream blobContent =\n        new ByteArrayInputStream(\"some BLOB content\".getBytes(StandardCharsets.UTF_8));\n    DescriptorDigest testBlobDigest = Digests.computeDigest(blobContent).getDigest();\n    blobContent.reset();\n\n    Response mockResponse = Mockito.mock(Response.class);\n    Mockito.when(mockResponse.getBody()).thenReturn(blobContent);\n\n    try {\n      testBlobPuller.handleResponse(mockResponse);\n      Assert.fail(\"Receiving an unexpected digest should fail\");\n\n    } catch (UnexpectedBlobDigestException ex) {\n      Assert.assertEquals(\n          \"The pulled BLOB has digest '\"\n              + testBlobDigest\n              + \"', but the request digest was '\"\n              + fakeDigest\n              + \"'\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testGetApiRoute() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"http://someApiBase/someImageName/blobs/\" + fakeDigest),\n        testBlobPuller.getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testGetActionDescription() {\n    Assert.assertEquals(\n        \"pull BLOB for someServerUrl/someImageName with digest \" + fakeDigest,\n        testBlobPuller.getActionDescription());\n  }\n\n  @Test\n  public void testGetHttpMethod() {\n    Assert.assertEquals(\"GET\", testBlobPuller.getHttpMethod());\n  }\n\n  @Test\n  public void testGetContent() {\n    Assert.assertNull(testBlobPuller.getContent());\n  }\n\n  @Test\n  public void testGetAccept() {\n    Assert.assertEquals(0, testBlobPuller.getAccept().size());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobPusherTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.GenericUrl;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.concurrent.atomic.LongAdder;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link BlobPusher}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class BlobPusherTest {\n\n  private static final String TEST_BLOB_CONTENT = \"some BLOB content\";\n  private static final Blob TEST_BLOB = Blobs.from(TEST_BLOB_CONTENT);\n\n  @Mock private URL mockUrl;\n  @Mock private Response mockResponse;\n\n  private DescriptorDigest fakeDescriptorDigest;\n  private BlobPusher testBlobPusher;\n\n  @Before\n  public void setUpFakes() throws DigestException {\n    fakeDescriptorDigest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n    testBlobPusher =\n        new BlobPusher(\n            new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\"),\n            fakeDescriptorDigest,\n            TEST_BLOB,\n            null);\n  }\n\n  @Test\n  public void testInitializer_getContent() {\n    Assert.assertNull(testBlobPusher.initializer().getContent());\n  }\n\n  @Test\n  public void testGetAccept() {\n    Assert.assertEquals(0, testBlobPusher.initializer().getAccept().size());\n  }\n\n  @Test\n  public void testInitializer_handleResponse_created() throws IOException, RegistryException {\n    Mockito.when(mockResponse.getStatusCode()).thenReturn(201); // Created\n    Assert.assertFalse(testBlobPusher.initializer().handleResponse(mockResponse).isPresent());\n  }\n\n  @Test\n  public void testInitializer_handleResponse_accepted() throws IOException, RegistryException {\n    Mockito.when(mockResponse.getStatusCode()).thenReturn(202); // Accepted\n    Mockito.when(mockResponse.getHeader(\"Location\"))\n        .thenReturn(Collections.singletonList(\"location\"));\n    GenericUrl requestUrl = new GenericUrl(\"https://someurl\");\n    Mockito.when(mockResponse.getRequestUrl()).thenReturn(requestUrl);\n    Assert.assertEquals(\n        new URL(\"https://someurl/location\"),\n        testBlobPusher.initializer().handleResponse(mockResponse).get());\n  }\n\n  @Test\n  public void testInitializer_handleResponse_accepted_multipleLocations()\n      throws IOException, RegistryException {\n    Mockito.when(mockResponse.getStatusCode()).thenReturn(202); // Accepted\n    Mockito.when(mockResponse.getHeader(\"Location\"))\n        .thenReturn(Arrays.asList(\"location1\", \"location2\"));\n    try {\n      testBlobPusher.initializer().handleResponse(mockResponse);\n      Assert.fail(\"Multiple 'Location' headers should be a registry error\");\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\"Expected 1 'Location' header, but found 2\"));\n    }\n  }\n\n  @Test\n  public void testInitializer_handleResponse_unrecognized() throws IOException, RegistryException {\n    Mockito.when(mockResponse.getStatusCode()).thenReturn(-1); // Unrecognized\n    try {\n      testBlobPusher.initializer().handleResponse(mockResponse);\n      Assert.fail(\"Multiple 'Location' headers should be a registry error\");\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"Received unrecognized status code -1\"));\n    }\n  }\n\n  @Test\n  public void testInitializer_getApiRoute_nullSource() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"http://someApiBase/someImageName/blobs/uploads/\"),\n        testBlobPusher.initializer().getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testInitializer_getApiRoute_sameSource() throws MalformedURLException {\n    testBlobPusher =\n        new BlobPusher(\n            new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\"),\n            fakeDescriptorDigest,\n            TEST_BLOB,\n            \"sourceImageName\");\n\n    Assert.assertEquals(\n        new URL(\n            \"http://someApiBase/someImageName/blobs/uploads/?mount=\"\n                + fakeDescriptorDigest\n                + \"&from=sourceImageName\"),\n        testBlobPusher.initializer().getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testInitializer_getHttpMethod() {\n    Assert.assertEquals(\"POST\", testBlobPusher.initializer().getHttpMethod());\n  }\n\n  @Test\n  public void testInitializer_getActionDescription() {\n    Assert.assertEquals(\n        \"push BLOB for someServerUrl/someImageName with digest \" + fakeDescriptorDigest,\n        testBlobPusher.initializer().getActionDescription());\n  }\n\n  @Test\n  public void testWriter_getContent() throws IOException {\n    LongAdder byteCount = new LongAdder();\n    BlobHttpContent body = testBlobPusher.writer(mockUrl, byteCount::add).getContent();\n\n    Assert.assertNotNull(body);\n    Assert.assertEquals(\"application/octet-stream\", body.getType());\n\n    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n    body.writeTo(byteArrayOutputStream);\n\n    Assert.assertEquals(\n        TEST_BLOB_CONTENT, new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8));\n    Assert.assertEquals(TEST_BLOB_CONTENT.length(), byteCount.sum());\n  }\n\n  @Test\n  public void testWriter_GetAccept() {\n    Assert.assertEquals(0, testBlobPusher.writer(mockUrl, ignored -> {}).getAccept().size());\n  }\n\n  @Test\n  public void testWriter_handleResponse() throws IOException, RegistryException {\n    Mockito.when(mockResponse.getHeader(\"Location\"))\n        .thenReturn(Collections.singletonList(\"https://somenewurl/location\"));\n    GenericUrl requestUrl = new GenericUrl(\"https://someurl\");\n    Mockito.when(mockResponse.getRequestUrl()).thenReturn(requestUrl);\n    Assert.assertEquals(\n        new URL(\"https://somenewurl/location\"),\n        testBlobPusher.writer(mockUrl, ignored -> {}).handleResponse(mockResponse));\n  }\n\n  @Test\n  public void testWriter_getApiRoute() throws MalformedURLException {\n    URL fakeUrl = new URL(\"http://someurl\");\n    Assert.assertEquals(fakeUrl, testBlobPusher.writer(fakeUrl, ignored -> {}).getApiRoute(\"\"));\n  }\n\n  @Test\n  public void testWriter_getHttpMethod() {\n    Assert.assertEquals(\"PATCH\", testBlobPusher.writer(mockUrl, ignored -> {}).getHttpMethod());\n  }\n\n  @Test\n  public void testWriter_getActionDescription() {\n    Assert.assertEquals(\n        \"push BLOB for someServerUrl/someImageName with digest \" + fakeDescriptorDigest,\n        testBlobPusher.writer(mockUrl, ignored -> {}).getActionDescription());\n  }\n\n  @Test\n  public void testCommitter_getContent() {\n    Assert.assertNull(testBlobPusher.committer(mockUrl).getContent());\n  }\n\n  @Test\n  public void testCommitter_GetAccept() {\n    Assert.assertEquals(0, testBlobPusher.committer(mockUrl).getAccept().size());\n  }\n\n  @Test\n  public void testCommitter_handleResponse() throws IOException, RegistryException {\n    Assert.assertNull(\n        testBlobPusher.committer(mockUrl).handleResponse(Mockito.mock(Response.class)));\n  }\n\n  @Test\n  public void testCommitter_getApiRoute() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"https://someurl?somequery=somevalue&digest=\" + fakeDescriptorDigest),\n        testBlobPusher.committer(new URL(\"https://someurl?somequery=somevalue\")).getApiRoute(\"\"));\n  }\n\n  @Test\n  public void testCommitter_getHttpMethod() {\n    Assert.assertEquals(\"PUT\", testBlobPusher.committer(mockUrl).getHttpMethod());\n  }\n\n  @Test\n  public void testCommitter_getActionDescription() {\n    Assert.assertEquals(\n        \"push BLOB for someServerUrl/someImageName with digest \" + fakeDescriptorDigest,\n        testBlobPusher.committer(mockUrl).getActionDescription());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/DockerRegistryBearerTokenTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.http.Authorization;\nimport com.google.common.collect.Multimap;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport org.junit.Assert;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\n/**\n * Tests for {@link RegistryClient} around handling of Docker Registry Bearer Tokens.\n *\n * <p>JWTs were generated from <a href=\"https://jwt.io\">jwt.io</a>'s <em>JWT Debugger</em>. Set the\n * algorithm to HS256, and paste the JSON shown as the <em>Payload</em>.\n */\npublic class DockerRegistryBearerTokenTest {\n  @Test\n  public void testDecode_dockerToken() {\n    // A genuine token from accessing docker.io's openjdk:\n    //   {\"access\":[{\"type\":\"repository\",\"name\":\"library/openjdk\",\"actions\":[\"pull\"]}]\n    // Generated by\n    // $ cd examples/helloworld\n    // $ mvn package jib:dockerBuild -Djib.from.image=openjdk \\\n    //    -Djava.util.logging.config.file=<faq-network-trace>\n    Multimap<String, String> decoded =\n        RegistryClient.decodeTokenRepositoryGrants(\n            \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDK2pDQ0FwK2dBd0lCQWdJQkFEQUtCZ2dxaGtqT1BRUURBakJHTVVRd1FnWURWUVFERXpzeVYwNVpPbFZMUzFJNlJFMUVVanBTU1U5Rk9reEhOa0U2UTFWWVZEcE5SbFZNT2tZelNFVTZOVkF5VlRwTFNqTkdPa05CTmxrNlNrbEVVVEFlRncweE9UQXhNVEl3TURJeU5EVmFGdzB5TURBeE1USXdNREl5TkRWYU1FWXhSREJDQmdOVkJBTVRPMUpMTkZNNlMwRkxVVHBEV0RWRk9rRTJSMVE2VTBwTVR6cFFNbEpMT2tOWlZVUTZTMEpEU0RwWFNVeE1Pa3hUU2xrNldscFFVVHBaVWxsRU1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcjY2bXkveXpHN21VUzF3eFQ3dFplS2pqRzcvNnBwZFNMY3JCcko5VytwcndzMGtIUDVwUHRkMUpkcFdEWU1OZWdqQXhpUWtRUUNvd25IUnN2ODVUalBUdE5wUkdKVTRkeHJkeXBvWGc4TVhYUEUzL2lRbHhPS2VNU0prNlRKbG5wNGFtWVBHQlhuQXRoQzJtTlR5ak1zdFh2ZmNWN3VFYWpRcnlOVUcyUVdXQ1k1Ujl0a2k5ZG54Z3dCSEF6bG8wTzJCczFmcm5JbmJxaCtic3ZSZ1FxU3BrMWhxYnhSU3AyRlNrL2tBL1gyeUFxZzJQSUJxWFFMaTVQQ3krWERYZElJczV6VG9ZbWJUK0pmbnZaMzRLcG5mSkpNalpIRW4xUVJtQldOZXJZcVdtNVhkQVhUMUJrQU9aditMNFVwSTk3NFZFZ2ppY1JINVdBeWV4b1BFclRRSURBUUFCbzRHeU1JR3ZNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVBCZ05WSFNVRUNEQUdCZ1JWSFNVQU1FUUdBMVVkRGdROUJEdFNTelJUT2t0QlMxRTZRMWcxUlRwQk5rZFVPbE5LVEU4NlVESlNTenBEV1ZWRU9rdENRMGc2VjBsTVREcE1VMHBaT2xwYVVGRTZXVkpaUkRCR0JnTlZIU01FUHpBOWdEc3lWMDVaT2xWTFMxSTZSRTFFVWpwU1NVOUZPa3hITmtFNlExVllWRHBOUmxWTU9rWXpTRVU2TlZBeVZUcExTak5HT2tOQk5sazZTa2xFVVRBS0JnZ3Foa2pPUFFRREFnTkpBREJHQWlFQXFOSXEwMFdZTmM5Z2tDZGdSUzRSWUhtNTRZcDBTa05Rd2lyMm5hSWtGd3dDSVFEMjlYdUl5TmpTa1cvWmpQaFlWWFB6QW9TNFVkRXNvUUhyUVZHMDd1N3ZsUT09Il19\"\n                + \".eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImxpYnJhcnkvb3BlbmpkayIsImFjdGlvbnMiOlsicHVsbCJdfV0sImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5pbyIsImV4cCI6MTU2MTA0MzkwNSwiaWF0IjoxNTYxMDQzNjA1LCJpc3MiOiJhdXRoLmRvY2tlci5pbyIsImp0aSI6Ikc5bWpiOE9GeU5STFlpY3ZUMFZxIiwibmJmIjoxNTYxMDQzMzA1LCJzdWIiOiIifQ\"\n                + \".jblwG_taIVf3IRiv200ivsc8q_IUj-M9QePKPAULfXdSZlY6H9n_XWtT6lw43k-J6QHfmnY4Yuh3eZq61KS7AT9yggM1VuolRCvYztSZ-MZHMIlvSE2KCc0wXa5gNQarjmDJloYduZuyLaKaRUUbO4osk1MuruODY_c2g2j16ce0Z8XVJ-7R8_J_Z8g0GdtFAfPO4bqpg9dj31MA8AKl3h-ru8NXcs3y1PkrYHpEGCgpcGcUQwLY7uiIrzjr0trCUbsLsv6iq2XTXnN_tTrfvL1R3yTB6gITvXZdsnU3r_UIDTzexTtlZWdntucJAGKX9HMA_jYEcTZ4ZhyEzETGpw\");\n    Assert.assertEquals(1, decoded.size());\n    Assert.assertTrue(decoded.containsEntry(\"library/openjdk\", \"pull\"));\n    Assert.assertFalse(decoded.containsEntry(\"library/openjdk\", \"push\"));\n    Assert.assertFalse(decoded.containsEntry(\"randomrepo\", \"push\"));\n  }\n\n  @Test\n  public void testDecode_nonToken() {\n    String base64Text =\n        Base64.getEncoder()\n            .encodeToString(\"something other than a JWT token\".getBytes(StandardCharsets.UTF_8));\n    Multimap<String, String> decoded = RegistryClient.decodeTokenRepositoryGrants(base64Text);\n    Assert.assertNull(decoded);\n  }\n\n  @Test\n  public void testDecode_invalidToken_accessString() {\n    // a JWT with an \"access\" field that is not an array: {\"access\": \"string\"}\n    String jwt =\n        \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJzdHJpbmcifQ.12ODBkkfh6J79qEejxwlD5AfOa9mjObPCzOnUL75NSQ\";\n    Multimap<String, String> decoded = RegistryClient.decodeTokenRepositoryGrants(jwt);\n    Assert.assertNull(decoded);\n  }\n\n  @Test\n  public void testDecode_invalidToken_accessArray() {\n    // a JWT with an \"access\" field that is an array of non-claim objects: {\"access\":[\"string\"]}\n    String jwt =\n        \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOlsic3RyaW5nIl19.gWZ9J4sO_w0hIVVxrfuuUC2lNhqkU3P0_z46xMCXfwU\";\n    Multimap<String, String> decoded = RegistryClient.decodeTokenRepositoryGrants(jwt);\n    Assert.assertNull(decoded);\n  }\n\n  @Test\n  @Ignore(\"Annotate AccessClaim.actions to disallow coercion of integers to strings\")\n  public void testDecode_invalidToken_actionsArray() {\n    // a JWT with an \"access\" field that is an action array of non-strings:\n    // {\"access\":[{\"type\": \"repository\",\"name\": \"library/openjdk\",\"actions\":[1]}]}\n    String jwt =\n        \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImxpYnJhcnkvb3BlbmpkayIsImFjdGlvbnMiOlsxXX1dfQ.12HZGeFvthXw0PP9ZKdttJRh2qsRfFNTeZV3_lZiI10\";\n    Multimap<String, String> decoded = RegistryClient.decodeTokenRepositoryGrants(jwt);\n    Assert.assertNull(decoded);\n  }\n\n  @Test\n  public void testDecode_invalidToken_randoJwt() {\n    // the JWT example token from jwt.io\n    String jwt =\n        \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\";\n    Multimap<String, String> decoded = RegistryClient.decodeTokenRepositoryGrants(jwt);\n    Assert.assertNull(decoded);\n  }\n\n  /** Basic credential should allow access to all. */\n  @Test\n  public void testCanAttemptBlobMount_basicCredential() {\n    Authorization fixture = Authorization.fromBasicCredentials(\"foo\", \"bar\");\n    Assert.assertTrue(RegistryClient.canAttemptBlobMount(fixture, \"random\"));\n    Assert.assertTrue(RegistryClient.canAttemptBlobMount(fixture, \"library/openjdk\"));\n  }\n\n  @Test\n  public void testCanAttemptBlobMount_bearer_withToken() {\n    // a synthetic token for accessing docker.io's openjdk with push and pull\n    // {\"access\":[{\"type\":\"repository\",\"name\":\"library/openjdk\",\"actions\":[\"pull\",\"push\"]}]}\n    String token =\n        \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6ImxpYnJhcnkvb3BlbmpkayIsImFjdGlvbnMiOlsicHVsbCIsInB1c2giXX1dfQ.VEn96Ug4eseKHX3WwP3PlgR9P7Y6VuYmMm-YRUjngFg\";\n    Authorization authorization = Authorization.fromBearerToken(token);\n    Assert.assertNotNull(authorization);\n    Assert.assertTrue(RegistryClient.canAttemptBlobMount(authorization, \"library/openjdk\"));\n    Assert.assertFalse(RegistryClient.canAttemptBlobMount(authorization, \"randomrepo\"));\n  }\n\n  @Test\n  public void testCanAttemptBlobMount_bearer_withNonToken() {\n    // non-Docker Registry Bearer Tokens are assumed to allow access to all\n    // the JWT example token from jwt.io\n    String jwt =\n        \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\";\n    Authorization authorization = Authorization.fromBearerToken(jwt);\n    Assert.assertNotNull(authorization);\n    Assert.assertTrue(RegistryClient.canAttemptBlobMount(authorization, \"library/openjdk\"));\n    Assert.assertTrue(RegistryClient.canAttemptBlobMount(authorization, \"randomrepo\"));\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/ErrorResponseUtilTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Test for {@link ErrorReponseUtil}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ErrorResponseUtilTest {\n\n  @Mock private ResponseException responseException;\n\n  @Test\n  public void testGetErrorCode_knownErrorCode() throws ResponseException {\n    Mockito.when(responseException.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\":[{\\\"code\\\":\\\"MANIFEST_INVALID\\\",\\\"message\\\":\\\"manifest invalid\\\",\\\"detail\\\":{}}]}\");\n\n    Assert.assertSame(\n        ErrorCodes.MANIFEST_INVALID, ErrorResponseUtil.getErrorCode(responseException));\n  }\n\n  /** An unknown {@link ErrorCodes} should cause original exception to be rethrown. */\n  @Test\n  public void testGetErrorCode_unknownErrorCode() {\n    Mockito.when(responseException.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\":[{\\\"code\\\":\\\"INVALID_ERROR_CODE\\\",\\\"message\\\":\\\"invalid code\\\",\\\"detail\\\":{}}]}\");\n    try {\n      ErrorResponseUtil.getErrorCode(responseException);\n      Assert.fail();\n    } catch (ResponseException ex) {\n      Assert.assertSame(responseException, ex);\n    }\n  }\n\n  /** Multiple error objects should cause original exception to be rethrown. */\n  @Test\n  public void testGetErrorCode_multipleErrors() {\n    Mockito.when(responseException.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\":[\"\n                + \"{\\\"code\\\":\\\"MANIFEST_INVALID\\\",\\\"message\\\":\\\"message 1\\\",\\\"detail\\\":{}},\"\n                + \"{\\\"code\\\":\\\"TAG_INVALID\\\",\\\"message\\\":\\\"message 2\\\",\\\"detail\\\":{}}\"\n                + \"]}\");\n    try {\n      ErrorResponseUtil.getErrorCode(responseException);\n      Assert.fail();\n    } catch (ResponseException ex) {\n      Assert.assertSame(responseException, ex);\n    }\n  }\n\n  /** An non-error object should cause original exception to be rethrown. */\n  @Test\n  public void testGetErrorCode_invalidErrorObject() {\n    Mockito.when(responseException.getContent())\n        .thenReturn(\"{\\\"type\\\":\\\"other\\\",\\\"message\\\":\\\"some other object\\\"}\");\n    try {\n      ErrorResponseUtil.getErrorCode(responseException);\n      Assert.fail();\n    } catch (ResponseException ex) {\n      Assert.assertSame(responseException, ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPullerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.OciIndexTemplate;\nimport com.google.cloud.tools.jib.image.json.OciManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.UnknownManifestFormatException;\nimport com.google.cloud.tools.jib.image.json.V21ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ManifestPuller}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ManifestPullerTest {\n\n  private static InputStream stringToInputStreamUtf8(String string) {\n    return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8));\n  }\n\n  private final RegistryEndpointRequestProperties fakeRegistryEndpointRequestProperties =\n      new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\");\n  private final ManifestPuller<ManifestTemplate> testManifestPuller =\n      new ManifestPuller<>(\n          fakeRegistryEndpointRequestProperties, \"test-image-tag\", ManifestTemplate.class);\n\n  @Mock private Response mockResponse;\n\n  @Test\n  public void testHandleResponse_v21()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path v21ManifestFile = Paths.get(Resources.getResource(\"core/json/v21manifest.json\").toURI());\n    InputStream v21Manifest = new ByteArrayInputStream(Files.readAllBytes(v21ManifestFile));\n\n    DescriptorDigest expectedDigest = Digests.computeDigest(v21Manifest).getDigest();\n    v21Manifest.reset();\n\n    Mockito.when(mockResponse.getBody()).thenReturn(v21Manifest);\n    ManifestAndDigest<?> manifestAndDigest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", V21ManifestTemplate.class)\n            .handleResponse(mockResponse);\n\n    MatcherAssert.assertThat(\n        manifestAndDigest.getManifest(), CoreMatchers.instanceOf(V21ManifestTemplate.class));\n    Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest());\n  }\n\n  @Test\n  public void testHandleResponse_v22()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path v22ManifestFile = Paths.get(Resources.getResource(\"core/json/v22manifest.json\").toURI());\n    InputStream v22Manifest = new ByteArrayInputStream(Files.readAllBytes(v22ManifestFile));\n\n    DescriptorDigest expectedDigest = Digests.computeDigest(v22Manifest).getDigest();\n    v22Manifest.reset();\n\n    Mockito.when(mockResponse.getBody()).thenReturn(v22Manifest);\n    ManifestAndDigest<?> manifestAndDigest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", V22ManifestTemplate.class)\n            .handleResponse(mockResponse);\n\n    MatcherAssert.assertThat(\n        manifestAndDigest.getManifest(), CoreMatchers.instanceOf(V22ManifestTemplate.class));\n    Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest());\n  }\n\n  @Test\n  public void testHandleResponse_ociManifest()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path ociManifestFile = Paths.get(Resources.getResource(\"core/json/ocimanifest.json\").toURI());\n    InputStream ociManifest = new ByteArrayInputStream(Files.readAllBytes(ociManifestFile));\n\n    DescriptorDigest expectedDigest = Digests.computeDigest(ociManifest).getDigest();\n    ociManifest.reset();\n\n    Mockito.when(mockResponse.getBody()).thenReturn(ociManifest);\n    ManifestAndDigest<?> manifestAndDigest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", OciManifestTemplate.class)\n            .handleResponse(mockResponse);\n\n    MatcherAssert.assertThat(\n        manifestAndDigest.getManifest(), CoreMatchers.instanceOf(OciManifestTemplate.class));\n    Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest());\n  }\n\n  @Test\n  public void testHandleResponse_v22ManifestListFailsWhenParsedAsV22Manifest()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path v22ManifestListFile =\n        Paths.get(Resources.getResource(\"core/json/v22manifest_list.json\").toURI());\n    InputStream v22ManifestList = new ByteArrayInputStream(Files.readAllBytes(v22ManifestListFile));\n\n    Mockito.when(mockResponse.getBody()).thenReturn(v22ManifestList);\n    try {\n      new ManifestPuller<>(\n              fakeRegistryEndpointRequestProperties, \"test-image-tag\", V22ManifestTemplate.class)\n          .handleResponse(mockResponse);\n      Assert.fail();\n    } catch (ClassCastException ex) {\n      // pass\n    }\n  }\n\n  @Test\n  public void testHandleResponse_v22ManifestListFromParentType()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path v22ManifestListFile =\n        Paths.get(Resources.getResource(\"core/json/v22manifest_list.json\").toURI());\n    InputStream v22ManifestList = new ByteArrayInputStream(Files.readAllBytes(v22ManifestListFile));\n    DescriptorDigest expectedDigest = Digests.computeDigest(v22ManifestList).getDigest();\n    v22ManifestList.reset();\n\n    Mockito.when(mockResponse.getBody()).thenReturn(v22ManifestList);\n    ManifestAndDigest<?> manifestAndDigest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", ManifestTemplate.class)\n            .handleResponse(mockResponse);\n    ManifestTemplate manifestTemplate = manifestAndDigest.getManifest();\n\n    MatcherAssert.assertThat(\n        manifestTemplate, CoreMatchers.instanceOf(V22ManifestListTemplate.class));\n    Assert.assertTrue(((V22ManifestListTemplate) manifestTemplate).getManifests().size() > 0);\n    Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest());\n  }\n\n  @Test\n  public void testHandleResponse_v22ManifestList()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path v22ManifestListFile =\n        Paths.get(Resources.getResource(\"core/json/v22manifest_list.json\").toURI());\n    InputStream v22ManifestList = new ByteArrayInputStream(Files.readAllBytes(v22ManifestListFile));\n\n    DescriptorDigest expectedDigest = Digests.computeDigest(v22ManifestList).getDigest();\n    v22ManifestList.reset();\n\n    Mockito.when(mockResponse.getBody()).thenReturn(v22ManifestList);\n    ManifestAndDigest<V22ManifestListTemplate> manifestAndDigest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties,\n                \"test-image-tag\",\n                V22ManifestListTemplate.class)\n            .handleResponse(mockResponse);\n    V22ManifestListTemplate manifestTemplate = manifestAndDigest.getManifest();\n\n    MatcherAssert.assertThat(\n        manifestTemplate, CoreMatchers.instanceOf(V22ManifestListTemplate.class));\n    Assert.assertTrue(manifestTemplate.getManifests().size() > 0);\n    Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest());\n  }\n\n  @Test\n  public void testHandleResponse_OciIndex()\n      throws URISyntaxException, IOException, UnknownManifestFormatException {\n    Path ociIndexFile =\n        Paths.get(Resources.getResource(\"core/json/ociindex_platforms.json\").toURI());\n    InputStream ociIndex = new ByteArrayInputStream(Files.readAllBytes(ociIndexFile));\n\n    DescriptorDigest expectedDigest = Digests.computeDigest(ociIndex).getDigest();\n    ociIndex.reset();\n\n    Mockito.when(mockResponse.getBody()).thenReturn(ociIndex);\n    ManifestAndDigest<OciIndexTemplate> manifestAndDigest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", OciIndexTemplate.class)\n            .handleResponse(mockResponse);\n    OciIndexTemplate manifestTemplate = manifestAndDigest.getManifest();\n\n    MatcherAssert.assertThat(manifestTemplate, CoreMatchers.instanceOf(OciIndexTemplate.class));\n    Assert.assertTrue(manifestTemplate.getManifests().size() > 0);\n    Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest());\n  }\n\n  @Test\n  public void testHandleResponse_noSchemaVersion() throws IOException {\n    Mockito.when(mockResponse.getBody()).thenReturn(stringToInputStreamUtf8(\"{}\"));\n    try {\n      testManifestPuller.handleResponse(mockResponse);\n      Assert.fail(\"An empty manifest should throw an error\");\n\n    } catch (UnknownManifestFormatException ex) {\n      Assert.assertEquals(\"Cannot find field 'schemaVersion' in manifest\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testHandleResponse_invalidSchemaVersion() throws IOException {\n    Mockito.when(mockResponse.getBody())\n        .thenReturn(stringToInputStreamUtf8(\"{\\\"schemaVersion\\\":\\\"not valid\\\"}\"));\n    try {\n      testManifestPuller.handleResponse(mockResponse);\n      Assert.fail(\"A non-integer schemaVersion should throw an error\");\n\n    } catch (UnknownManifestFormatException ex) {\n      Assert.assertEquals(\"'schemaVersion' field is not an integer\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testHandleResponse_unknownSchemaVersion() throws IOException {\n    Mockito.when(mockResponse.getBody())\n        .thenReturn(stringToInputStreamUtf8(\"{\\\"schemaVersion\\\":0}\"));\n    try {\n      testManifestPuller.handleResponse(mockResponse);\n      Assert.fail(\"An unknown manifest schemaVersion should throw an error\");\n\n    } catch (UnknownManifestFormatException ex) {\n      Assert.assertEquals(\"Unknown schemaVersion: 0 - only 1 and 2 are supported\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testHandleResponse_ociIndexWithNoMediaType()\n      throws IOException, UnknownManifestFormatException {\n    String ociManifestJson =\n        \"{\\n\"\n            + \"  \\\"schemaVersion\\\": 2,\\n\"\n            + \"  \\\"manifests\\\": [\\n\"\n            + \"    {\\n\"\n            + \"      \\\"mediaType\\\": \\\"application/vnd.oci.image.manifest.v1+json\\\",\\n\"\n            + \"      \\\"size\\\": 7143,\\n\"\n            + \"      \\\"digest\\\": \\\"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\\\"\\n\"\n            + \"    }\\n\"\n            + \"  ]\\n\"\n            + \"}\";\n    Mockito.when(mockResponse.getBody()).thenReturn(stringToInputStreamUtf8(ociManifestJson));\n\n    ManifestTemplate manifest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", ManifestTemplate.class)\n            .handleResponse(mockResponse)\n            .getManifest();\n\n    MatcherAssert.assertThat(manifest, CoreMatchers.instanceOf(OciIndexTemplate.class));\n    OciIndexTemplate ociIndex = (OciIndexTemplate) manifest;\n\n    Assert.assertEquals(\"application/vnd.oci.image.index.v1+json\", manifest.getManifestMediaType());\n    Assert.assertEquals(1, ociIndex.getManifests().size());\n    Assert.assertEquals(\n        \"e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n        ociIndex.getManifests().get(0).getDigest().getHash());\n  }\n\n  @Test\n  public void testHandleResponse_ociManfiestWithNoMediaType()\n      throws IOException, UnknownManifestFormatException {\n    String ociManifestJson =\n        \"{\\n\"\n            + \"  \\\"schemaVersion\\\": 2,\\n\"\n            + \"  \\\"config\\\": {\\n\"\n            + \"    \\\"mediaType\\\": \\\"application/vnd.oci.image.config.v1+json\\\",\\n\"\n            + \"    \\\"size\\\": 7023,\\n\"\n            + \"    \\\"digest\\\": \\\"sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\\\"\\n\"\n            + \"  },\\n\"\n            + \"  \\\"layers\\\": []\\n\"\n            + \"}\";\n    Mockito.when(mockResponse.getBody()).thenReturn(stringToInputStreamUtf8(ociManifestJson));\n\n    ManifestTemplate manifest =\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", ManifestTemplate.class)\n            .handleResponse(mockResponse)\n            .getManifest();\n\n    MatcherAssert.assertThat(manifest, CoreMatchers.instanceOf(OciManifestTemplate.class));\n    OciManifestTemplate ociManifest = (OciManifestTemplate) manifest;\n\n    Assert.assertEquals(\n        \"application/vnd.oci.image.manifest.v1+json\", manifest.getManifestMediaType());\n    Assert.assertEquals(\n        \"b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\",\n        ociManifest.getContainerConfiguration().getDigest().getHash());\n  }\n\n  @Test\n  public void testHandleResponse_invalidOciManfiest() throws IOException {\n    Mockito.when(mockResponse.getBody())\n        .thenReturn(stringToInputStreamUtf8(\"{\\\"schemaVersion\\\": 2}\"));\n\n    ManifestPuller<ManifestTemplate> manifestPuller =\n        new ManifestPuller<>(\n            fakeRegistryEndpointRequestProperties, \"test-image-tag\", ManifestTemplate.class);\n    try {\n      manifestPuller.handleResponse(mockResponse);\n      Assert.fail();\n    } catch (UnknownManifestFormatException ex) {\n      Assert.assertEquals(\n          \"'schemaVersion' is 2, but neither 'manifests' nor 'config' exists\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testGetApiRoute() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"http://someApiBase/someImageName/manifests/test-image-tag\"),\n        testManifestPuller.getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testGetHttpMethod() {\n    Assert.assertEquals(\"GET\", testManifestPuller.getHttpMethod());\n  }\n\n  @Test\n  public void testGetActionDescription() {\n    Assert.assertEquals(\n        \"pull image manifest for someServerUrl/someImageName:test-image-tag\",\n        testManifestPuller.getActionDescription());\n  }\n\n  @Test\n  public void testGetContent() {\n    Assert.assertNull(testManifestPuller.getContent());\n  }\n\n  @Test\n  public void testGetAccept() {\n    Assert.assertEquals(\n        Arrays.asList(\n            OciManifestTemplate.MANIFEST_MEDIA_TYPE,\n            V22ManifestTemplate.MANIFEST_MEDIA_TYPE,\n            V21ManifestTemplate.MEDIA_TYPE,\n            V22ManifestListTemplate.MANIFEST_MEDIA_TYPE,\n            OciIndexTemplate.MEDIA_TYPE),\n        testManifestPuller.getAccept());\n\n    Assert.assertEquals(\n        Collections.singletonList(OciManifestTemplate.MANIFEST_MEDIA_TYPE),\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", OciManifestTemplate.class)\n            .getAccept());\n    Assert.assertEquals(\n        Collections.singletonList(V22ManifestTemplate.MANIFEST_MEDIA_TYPE),\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", V22ManifestTemplate.class)\n            .getAccept());\n    Assert.assertEquals(\n        Collections.singletonList(V21ManifestTemplate.MEDIA_TYPE),\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", V21ManifestTemplate.class)\n            .getAccept());\n    Assert.assertEquals(\n        Collections.singletonList(V22ManifestListTemplate.MANIFEST_MEDIA_TYPE),\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties,\n                \"test-image-tag\",\n                V22ManifestListTemplate.class)\n            .getAccept());\n    Assert.assertEquals(\n        Collections.singletonList(OciIndexTemplate.MEDIA_TYPE),\n        new ManifestPuller<>(\n                fakeRegistryEndpointRequestProperties, \"test-image-tag\", OciIndexTemplate.class)\n            .getAccept());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPusherTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.hash.Digests;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.apache.http.HttpStatus;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ManifestPusher}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ManifestPusherTest {\n\n  @Mock private Response mockResponse;\n  @Mock private EventHandlers mockEventHandlers;\n\n  private Path v22manifestJsonFile;\n  private V22ManifestTemplate fakeManifestTemplate;\n  private ManifestPusher testManifestPusher;\n\n  @Before\n  public void setUp() throws URISyntaxException, IOException {\n    v22manifestJsonFile = Paths.get(Resources.getResource(\"core/json/v22manifest.json\").toURI());\n    fakeManifestTemplate =\n        JsonTemplateMapper.readJsonFromFile(v22manifestJsonFile, V22ManifestTemplate.class);\n\n    testManifestPusher =\n        new ManifestPusher(\n            new RegistryEndpointRequestProperties(\"someServerUrl\", \"someImageName\"),\n            fakeManifestTemplate,\n            \"test-image-tag\",\n            mockEventHandlers);\n  }\n\n  @Test\n  public void testGetContent() throws IOException {\n    BlobHttpContent body = testManifestPusher.getContent();\n\n    Assert.assertNotNull(body);\n    Assert.assertEquals(V22ManifestTemplate.MANIFEST_MEDIA_TYPE, body.getType());\n\n    ByteArrayOutputStream bodyCaptureStream = new ByteArrayOutputStream();\n    body.writeTo(bodyCaptureStream);\n    String v22manifestJson =\n        new String(Files.readAllBytes(v22manifestJsonFile), StandardCharsets.UTF_8);\n    Assert.assertEquals(\n        v22manifestJson, new String(bodyCaptureStream.toByteArray(), StandardCharsets.UTF_8));\n  }\n\n  @Test\n  public void testHandleResponse_valid() throws IOException {\n    DescriptorDigest expectedDigest = Digests.computeJsonDigest(fakeManifestTemplate);\n    Mockito.when(mockResponse.getHeader(\"Docker-Content-Digest\"))\n        .thenReturn(Collections.singletonList(expectedDigest.toString()));\n    Assert.assertEquals(expectedDigest, testManifestPusher.handleResponse(mockResponse));\n  }\n\n  @Test\n  public void testHandleResponse_noDigest() throws IOException {\n    DescriptorDigest expectedDigest = Digests.computeJsonDigest(fakeManifestTemplate);\n    Mockito.when(mockResponse.getHeader(\"Docker-Content-Digest\"))\n        .thenReturn(Collections.emptyList());\n\n    Assert.assertEquals(expectedDigest, testManifestPusher.handleResponse(mockResponse));\n    Mockito.verify(mockEventHandlers)\n        .dispatch(LogEvent.warn(\"Expected image digest \" + expectedDigest + \", but received none\"));\n  }\n\n  @Test\n  public void testHandleResponse_multipleDigests() throws IOException {\n    DescriptorDigest expectedDigest = Digests.computeJsonDigest(fakeManifestTemplate);\n    Mockito.when(mockResponse.getHeader(\"Docker-Content-Digest\"))\n        .thenReturn(Arrays.asList(\"too\", \"many\"));\n\n    Assert.assertEquals(expectedDigest, testManifestPusher.handleResponse(mockResponse));\n    Mockito.verify(mockEventHandlers)\n        .dispatch(\n            LogEvent.warn(\"Expected image digest \" + expectedDigest + \", but received: too, many\"));\n  }\n\n  @Test\n  public void testHandleResponse_invalidDigest() throws IOException {\n    DescriptorDigest expectedDigest = Digests.computeJsonDigest(fakeManifestTemplate);\n    Mockito.when(mockResponse.getHeader(\"Docker-Content-Digest\"))\n        .thenReturn(Collections.singletonList(\"not valid\"));\n\n    Assert.assertEquals(expectedDigest, testManifestPusher.handleResponse(mockResponse));\n    Mockito.verify(mockEventHandlers)\n        .dispatch(\n            LogEvent.warn(\"Expected image digest \" + expectedDigest + \", but received: not valid\"));\n  }\n\n  @Test\n  public void testApiRoute() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"http://someApiBase/someImageName/manifests/test-image-tag\"),\n        testManifestPusher.getApiRoute(\"http://someApiBase/\"));\n  }\n\n  @Test\n  public void testGetHttpMethod() {\n    Assert.assertEquals(\"PUT\", testManifestPusher.getHttpMethod());\n  }\n\n  @Test\n  public void testGetActionDescription() {\n    Assert.assertEquals(\n        \"push image manifest for someServerUrl/someImageName:test-image-tag\",\n        testManifestPusher.getActionDescription());\n  }\n\n  @Test\n  public void testGetAccept() {\n    Assert.assertEquals(0, testManifestPusher.getAccept().size());\n  }\n\n  /** Docker Registry 2.0 and 2.1 return 400 / TAG_INVALID. */\n  @Test\n  public void testHandleHttpResponseException_dockerRegistry_tagInvalid() throws ResponseException {\n    ResponseException exception = Mockito.mock(ResponseException.class);\n    Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_BAD_REQUEST);\n    Mockito.when(exception.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\":[{\\\"code\\\":\\\"TAG_INVALID\\\",\"\n                + \"\\\"message\\\":\\\"manifest tag did not match URI\\\"}]}\");\n    try {\n      testManifestPusher.handleHttpResponseException(exception);\n      Assert.fail();\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Registry may not support pushing OCI Manifest or \"\n                  + \"Docker Image Manifest Version 2, Schema 2\"));\n    }\n  }\n\n  /** Docker Registry 2.2 returns a 400 / MANIFEST_INVALID. */\n  @Test\n  public void testHandleHttpResponseException_dockerRegistry_manifestInvalid()\n      throws ResponseException {\n    ResponseException exception = Mockito.mock(ResponseException.class);\n    Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_BAD_REQUEST);\n    Mockito.when(exception.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\":[{\\\"code\\\":\\\"MANIFEST_INVALID\\\",\"\n                + \"\\\"message\\\":\\\"manifest invalid\\\",\\\"detail\\\":{}}]}\");\n    try {\n      testManifestPusher.handleHttpResponseException(exception);\n      Assert.fail();\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Registry may not support pushing OCI Manifest or \"\n                  + \"Docker Image Manifest Version 2, Schema 2\"));\n    }\n  }\n\n  /** Quay.io returns an undocumented 415 / MANIFEST_INVALID. */\n  @Test\n  public void testHandleHttpResponseException_quayIo() throws ResponseException {\n    ResponseException exception = Mockito.mock(ResponseException.class);\n    Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE);\n    Mockito.when(exception.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\":[{\\\"code\\\":\\\"MANIFEST_INVALID\\\",\"\n                + \"\\\"detail\\\":{\\\"message\\\":\\\"manifest schema version not supported\\\"},\"\n                + \"\\\"message\\\":\\\"manifest invalid\\\"}]}\");\n    try {\n      testManifestPusher.handleHttpResponseException(exception);\n      Assert.fail();\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Registry may not support pushing OCI Manifest or \"\n                  + \"Docker Image Manifest Version 2, Schema 2\"));\n    }\n  }\n\n  @Test\n  public void testHandleHttpResponseException_otherError() throws RegistryErrorException {\n    ResponseException exception = Mockito.mock(ResponseException.class);\n    Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_UNAUTHORIZED);\n    try {\n      testManifestPusher.handleHttpResponseException(exception);\n      Assert.fail();\n\n    } catch (ResponseException ex) {\n      Assert.assertSame(exception, ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/PlainHttpClient.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.GenericUrl;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Request;\nimport com.google.cloud.tools.jib.http.Response;\nimport java.io.IOException;\nimport java.net.URL;\n\n/** Forces sending all requests in plain-HTTP protocol. For testing only. */\nclass PlainHttpClient extends FailoverHttpClient {\n\n  PlainHttpClient() {\n    super(true, true, ignored -> {});\n  }\n\n  @Override\n  public Response call(String httpMethod, URL url, Request request) throws IOException {\n    GenericUrl httpUrl = new GenericUrl(url);\n    httpUrl.setScheme(\"http\");\n    return super.call(httpMethod, httpUrl.toURL(), request);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAliasGroupTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.common.collect.Sets;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link RegistryAliasGroup}. */\npublic class RegistryAliasGroupTest {\n\n  @Test\n  public void testGetAliasesGroup_noKnownAliases() {\n    List<String> singleton = RegistryAliasGroup.getAliasesGroup(\"something.gcr.io\");\n    Assert.assertEquals(1, singleton.size());\n    Assert.assertEquals(\"something.gcr.io\", singleton.get(0));\n  }\n\n  @Test\n  public void testGetAliasesGroup_dockerHub() {\n    Set<String> aliases =\n        Sets.newHashSet(\n            \"registry.hub.docker.com\", \"index.docker.io\", \"registry-1.docker.io\", \"docker.io\");\n    for (String alias : aliases) {\n      Assert.assertEquals(aliases, new HashSet<>(RegistryAliasGroup.getAliasesGroup(alias)));\n    }\n  }\n\n  @Test\n  public void testGetHost_noAlias() {\n    String host = RegistryAliasGroup.getHost(\"something.gcr.io\");\n    Assert.assertEquals(\"something.gcr.io\", host);\n  }\n\n  @Test\n  public void testGetHost_dockerIo() {\n    String host = RegistryAliasGroup.getHost(\"docker.io\");\n    Assert.assertEquals(\"registry-1.docker.io\", host);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticationFailedExceptionTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link RegistryAuthenticationFailedException}. */\npublic class RegistryAuthenticationFailedExceptionTest {\n\n  @Test\n  public void testRegistryAuthenticationFailedException_message() {\n    RegistryAuthenticationFailedException exception =\n        new RegistryAuthenticationFailedException(\"serverUrl\", \"imageName\", \"message\");\n    Assert.assertEquals(\"serverUrl\", exception.getServerUrl());\n    Assert.assertEquals(\"imageName\", exception.getImageName());\n    Assert.assertEquals(\n        \"Failed to authenticate with registry serverUrl/imageName because: message\",\n        exception.getMessage());\n  }\n\n  @Test\n  public void testRegistryAuthenticationFailedException_exception() {\n    Throwable cause = new Throwable(\"message\");\n    RegistryAuthenticationFailedException exception =\n        new RegistryAuthenticationFailedException(\"serverUrl\", \"imageName\", cause);\n    Assert.assertEquals(\"serverUrl\", exception.getServerUrl());\n    Assert.assertEquals(\"imageName\", exception.getImageName());\n    Assert.assertSame(cause, exception.getCause());\n    Assert.assertEquals(\n        \"Failed to authenticate with registry serverUrl/imageName because: message\",\n        exception.getMessage());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.http.TestWebServer;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.security.GeneralSecurityException;\nimport java.util.Collections;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link RegistryAuthenticator}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class RegistryAuthenticatorTest {\n  private final RegistryEndpointRequestProperties registryEndpointRequestProperties =\n      new RegistryEndpointRequestProperties(\"someserver\", \"someimage\");\n\n  @Mock private FailoverHttpClient httpClient;\n  @Mock private Response response;\n\n  @Captor private ArgumentCaptor<URL> urlCaptor;\n\n  private RegistryAuthenticator registryAuthenticator;\n\n  @Before\n  public void setUp() throws RegistryAuthenticationFailedException, IOException {\n    registryAuthenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n                \"Bearer realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient)\n            .get();\n\n    ByteArrayInputStream tokenJson =\n        new ByteArrayInputStream(\"{\\\"token\\\":\\\"my_token\\\"}\".getBytes(StandardCharsets.UTF_8));\n    Mockito.when(response.getBody()).thenReturn(tokenJson);\n    Mockito.when(httpClient.call(Mockito.any(), urlCaptor.capture(), Mockito.any()))\n        .thenReturn(response);\n  }\n\n  @Test\n  public void testFromAuthenticationMethod_bearer()\n      throws MalformedURLException, RegistryAuthenticationFailedException {\n    RegistryAuthenticator registryAuthenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n                \"Bearer realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient)\n            .get();\n    assertThat(\n            registryAuthenticator.getAuthenticationUrl(\n                null, Collections.singletonMap(\"someimage\", \"scope\")))\n        .isEqualTo(\n            new URL(\"https://somerealm?service=someservice&scope=repository:someimage:scope\"));\n\n    registryAuthenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n                \"bEaReR realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient)\n            .get();\n    assertThat(\n            registryAuthenticator.getAuthenticationUrl(\n                null, Collections.singletonMap(\"someimage\", \"scope\")))\n        .isEqualTo(\n            new URL(\"https://somerealm?service=someservice&scope=repository:someimage:scope\"));\n  }\n\n  @Test\n  public void testAuthRequestParameters_basicAuth() {\n    Assert.assertEquals(\n        \"service=someservice&scope=repository:someimage:scope\",\n        registryAuthenticator.getAuthRequestParameters(\n            null, Collections.singletonMap(\"someimage\", \"scope\")));\n  }\n\n  @Test\n  public void testAuthRequestParameters_oauth2() {\n    Credential credential = Credential.from(\"<token>\", \"oauth2_access_token\");\n    Assert.assertEquals(\n        \"service=someservice&scope=repository:someimage:scope\"\n            + \"&client_id=jib.da031fe481a93ac107a95a96462358f9\"\n            + \"&grant_type=refresh_token&refresh_token=oauth2_access_token\",\n        registryAuthenticator.getAuthRequestParameters(\n            credential, Collections.singletonMap(\"someimage\", \"scope\")));\n  }\n\n  @Test\n  public void isOAuth2Auth_nullCredential() {\n    Assert.assertFalse(registryAuthenticator.isOAuth2Auth(null));\n  }\n\n  @Test\n  public void isOAuth2Auth_basicAuth() {\n    Credential credential = Credential.from(\"name\", \"password\");\n    Assert.assertFalse(registryAuthenticator.isOAuth2Auth(credential));\n  }\n\n  @Test\n  public void isOAuth2Auth_oauth2() {\n    Credential credential = Credential.from(\"<token>\", \"oauth2_token\");\n    Assert.assertTrue(registryAuthenticator.isOAuth2Auth(credential));\n  }\n\n  @Test\n  public void getAuthenticationUrl_basicAuth() throws MalformedURLException {\n    Assert.assertEquals(\n        new URL(\"https://somerealm?service=someservice&scope=repository:someimage:scope\"),\n        registryAuthenticator.getAuthenticationUrl(\n            null, Collections.singletonMap(\"someimage\", \"scope\")));\n  }\n\n  @Test\n  public void istAuthenticationUrl_oauth2() throws MalformedURLException {\n    Credential credential = Credential.from(\"<token>\", \"oauth2_token\");\n    Assert.assertEquals(\n        new URL(\"https://somerealm\"),\n        registryAuthenticator.getAuthenticationUrl(credential, Collections.emptyMap()));\n  }\n\n  @Test\n  public void testFromAuthenticationMethod_basic() throws RegistryAuthenticationFailedException {\n    assertThat(\n            RegistryAuthenticator.fromAuthenticationMethod(\n                \"Basic\", registryEndpointRequestProperties, \"user-agent\", httpClient))\n        .isEmpty();\n\n    assertThat(\n            RegistryAuthenticator.fromAuthenticationMethod(\n                \"Basic realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient))\n        .isEmpty();\n\n    assertThat(\n            RegistryAuthenticator.fromAuthenticationMethod(\n                \"BASIC realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient))\n        .isEmpty();\n\n    assertThat(\n            RegistryAuthenticator.fromAuthenticationMethod(\n                \"bASIC realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient))\n        .isEmpty();\n  }\n\n  @Test\n  public void testFromAuthenticationMethod_noBearer() {\n    try {\n      RegistryAuthenticator.fromAuthenticationMethod(\n          \"realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n          registryEndpointRequestProperties,\n          \"user-agent\",\n          httpClient);\n      Assert.fail(\"Authentication method without 'Bearer ' or 'Basic ' should fail\");\n\n    } catch (RegistryAuthenticationFailedException ex) {\n      Assert.assertEquals(\n          \"Failed to authenticate with registry someserver/someimage because: 'Bearer' was not found in the 'WWW-Authenticate' header, tried to parse: realm=\\\"https://somerealm\\\",service=\\\"someservice\\\",scope=\\\"somescope\\\"\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testFromAuthenticationMethod_noRealm() {\n    try {\n      RegistryAuthenticator.fromAuthenticationMethod(\n          \"Bearer scope=\\\"somescope\\\"\",\n          registryEndpointRequestProperties,\n          \"user-agent\",\n          httpClient);\n      Assert.fail(\"Authentication method without 'realm' should fail\");\n\n    } catch (RegistryAuthenticationFailedException ex) {\n      Assert.assertEquals(\n          \"Failed to authenticate with registry someserver/someimage because: 'realm' was not found in the 'WWW-Authenticate' header, tried to parse: Bearer scope=\\\"somescope\\\"\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testFromAuthenticationMethod_noService()\n      throws MalformedURLException, RegistryAuthenticationFailedException {\n    RegistryAuthenticator registryAuthenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n                \"Bearer realm=\\\"https://somerealm\\\"\",\n                registryEndpointRequestProperties,\n                \"user-agent\",\n                httpClient)\n            .get();\n\n    Assert.assertEquals(\n        new URL(\"https://somerealm?service=someserver&scope=repository:someimage:scope\"),\n        registryAuthenticator.getAuthenticationUrl(\n            null, Collections.singletonMap(\"someimage\", \"scope\")));\n  }\n\n  @Test\n  public void testUserAgent()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryCredentialsNotSentException {\n    try (TestWebServer server = new TestWebServer(false)) {\n      try {\n        RegistryAuthenticator authenticator =\n            RegistryAuthenticator.fromAuthenticationMethod(\n                    \"Bearer realm=\\\"\" + server.getEndpoint() + \"\\\"\",\n                    registryEndpointRequestProperties,\n                    \"Competent-Agent\",\n                    new FailoverHttpClient(true, false, ignored -> {}))\n                .get();\n        authenticator.authenticatePush(null);\n      } catch (RegistryAuthenticationFailedException ex) {\n        // Doesn't matter if auth fails. We only examine what we sent.\n      }\n      MatcherAssert.assertThat(\n          server.getInputRead(), CoreMatchers.containsString(\"User-Agent: Competent-Agent\"));\n    }\n  }\n\n  @Test\n  public void testSourceImage_differentSourceRepository()\n      throws RegistryCredentialsNotSentException, RegistryAuthenticationFailedException {\n    RegistryAuthenticator authenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n                \"Bearer realm=\\\"https://1.2.3.4:5\\\"\",\n                new RegistryEndpointRequestProperties(\"someserver\", \"someimage\", \"anotherimage\"),\n                \"Competent-Agent\",\n                httpClient)\n            .get();\n    authenticator.authenticatePush(null);\n    MatcherAssert.assertThat(\n        urlCaptor.getValue().toString(),\n        CoreMatchers.endsWith(\n            \"scope=repository:someimage:pull,push&scope=repository:anotherimage:pull\"));\n  }\n\n  @Test\n  public void testSourceImage_sameSourceRepository()\n      throws RegistryCredentialsNotSentException, RegistryAuthenticationFailedException {\n    RegistryAuthenticator authenticator =\n        RegistryAuthenticator.fromAuthenticationMethod(\n                \"Bearer realm=\\\"https://1.2.3.4:5\\\"\",\n                new RegistryEndpointRequestProperties(\"someserver\", \"someimage\", \"someimage\"),\n                \"Competent-Agent\",\n                httpClient)\n            .get();\n    authenticator.authenticatePush(null);\n    MatcherAssert.assertThat(\n        urlCaptor.getValue().toString(),\n        CoreMatchers.endsWith(\"service=someserver&scope=repository:someimage:pull,push\"));\n  }\n\n  @Test\n  public void testAuthorizationCleared() throws RegistryAuthenticationFailedException, IOException {\n    ResponseException responseException = Mockito.mock(ResponseException.class);\n    Mockito.when(responseException.getStatusCode()).thenReturn(401);\n    Mockito.when(responseException.requestAuthorizationCleared()).thenReturn(true);\n    Mockito.when(httpClient.call(Mockito.any(), Mockito.any(), Mockito.any()))\n        .thenThrow(responseException);\n\n    try {\n      registryAuthenticator.authenticatePush(null);\n      Assert.fail();\n    } catch (RegistryCredentialsNotSentException ex) {\n      Assert.assertEquals(\n          \"Required credentials for someserver/someimage were not sent because the connection was \"\n              + \"over HTTP\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testAuthenticationResponseTemplate_readsToken() throws IOException {\n    String input = \"{\\\"token\\\":\\\"test_value\\\"}\";\n    RegistryAuthenticator.AuthenticationResponseTemplate template =\n        JsonTemplateMapper.readJson(\n            input, RegistryAuthenticator.AuthenticationResponseTemplate.class);\n    Assert.assertEquals(\"test_value\", template.getToken());\n  }\n\n  @Test\n  public void testAuthenticationResponseTemplate_readsAccessToken() throws IOException {\n    String input = \"{\\\"access_token\\\":\\\"test_value\\\"}\";\n    RegistryAuthenticator.AuthenticationResponseTemplate template =\n        JsonTemplateMapper.readJson(\n            input, RegistryAuthenticator.AuthenticationResponseTemplate.class);\n    Assert.assertEquals(\"test_value\", template.getToken());\n  }\n\n  @Test\n  public void testAuthenticationResponseTemplate_prefersToken() throws IOException {\n    String input = \"{\\\"token\\\":\\\"test_value\\\",\\\"access_token\\\":\\\"wrong_value\\\"}\";\n    RegistryAuthenticator.AuthenticationResponseTemplate template =\n        JsonTemplateMapper.readJson(\n            input, RegistryAuthenticator.AuthenticationResponseTemplate.class);\n    Assert.assertEquals(\"test_value\", template.getToken());\n  }\n\n  @Test\n  public void testAuthenticationResponseTemplate_acceptsNull() throws IOException {\n    String input = \"{}\";\n    RegistryAuthenticator.AuthenticationResponseTemplate template =\n        JsonTemplateMapper.readJson(\n            input, RegistryAuthenticator.AuthenticationResponseTemplate.class);\n    Assert.assertNull(template.getToken());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryClientTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.blob.BlobDescriptor;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.TestWebServer;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.security.DigestException;\nimport java.security.GeneralSecurityException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentMatcher;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/**\n * Tests for {@link RegistryClient}. More comprehensive tests can be found in the integration tests.\n */\n@RunWith(MockitoJUnitRunner.class)\npublic class RegistryClientTest {\n\n  @Mock private EventHandlers eventHandlers;\n\n  private DescriptorDigest digest;\n\n  private TestWebServer registry;\n  private TestWebServer authServer;\n\n  @Before\n  public void setUp() throws DigestException {\n    digest =\n        DescriptorDigest.fromHash(\n            \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\");\n  }\n\n  @After\n  public void tearDown() throws IOException {\n    if (registry != null) {\n      registry.close();\n    }\n    if (authServer != null) {\n      authServer.close();\n    }\n  }\n\n  @Test\n  public void testDoBearerAuth_returnsFalseOnBasicAuth()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String basicAuth =\n        \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\nWWW-Authenticate: Basic foo\\n\\n\";\n    registry = new TestWebServer(false, Arrays.asList(basicAuth), 1);\n\n    RegistryClient registryClient = createRegistryClient(null);\n    Assert.assertFalse(registryClient.doPullBearerAuth());\n\n    Mockito.verify(eventHandlers).dispatch(logContains(\"attempting bearer auth\"));\n    Mockito.verify(eventHandlers).dispatch(logContains(\"server requires basic auth\"));\n  }\n\n  @Test\n  public void testDoBearerAuth()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    setUpAuthServerAndRegistry(1, \"HTTP/1.1 200 OK\\nContent-Length: 1234\\n\\n\");\n\n    RegistryClient registryClient = createRegistryClient(null);\n    Assert.assertTrue(registryClient.doPushBearerAuth());\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(1234, digestAndSize.get().getSize());\n\n    Mockito.verify(eventHandlers).dispatch(logContains(\"attempting bearer auth\"));\n    Mockito.verify(eventHandlers).dispatch(logContains(\"bearer auth succeeded\"));\n  }\n\n  @Test\n  public void testAutomaticTokenRefresh()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    setUpAuthServerAndRegistry(3, \"HTTP/1.1 200 OK\\nContent-Length: 5678\\n\\n\");\n\n    RegistryClient registryClient = createRegistryClient(null);\n    Assert.assertTrue(registryClient.doPushBearerAuth());\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(5678, digestAndSize.get().getSize());\n\n    // Verify authServer returned bearer token three times (i.e., refreshed twice)\n    Assert.assertEquals(3, authServer.getTotalResponsesServed());\n    Assert.assertEquals(4, registry.getTotalResponsesServed());\n\n    Mockito.verify(eventHandlers).dispatch(logContains(\"attempting bearer auth\"));\n    Mockito.verify(eventHandlers).dispatch(logContains(\"bearer auth succeeded\"));\n    Mockito.verify(eventHandlers, Mockito.times(2))\n        .dispatch(logContains(\"refreshing bearer auth token\"));\n  }\n\n  @Test\n  public void testAutomaticTokenRefresh_badWwwAuthenticateResponse()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String tokenResponse = \"HTTP/1.1 200 OK\\nContent-Length: 26\\n\\n{\\\"token\\\":\\\"awesome-token!\\\"}\";\n    authServer = new TestWebServer(false, Arrays.asList(tokenResponse), 3);\n\n    List<String> responses =\n        Arrays.asList(\n            \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\nWWW-Authenticate: Bearer realm=\\\"\"\n                + authServer.getEndpoint()\n                + \"\\\"\\n\\n\",\n            \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\nWWW-Authenticate: Basic realm=foo\\n\\n\",\n            \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\n\\n\",\n            \"HTTP/1.1 200 OK\\nContent-Length: 5678\\n\\n\");\n    registry = new TestWebServer(false, responses, responses.size(), true);\n\n    RegistryClient registryClient = createRegistryClient(null);\n    Assert.assertTrue(registryClient.doPushBearerAuth());\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(5678, digestAndSize.get().getSize());\n\n    // Verify authServer returned bearer token three times (i.e., refreshed twice)\n    Assert.assertEquals(3, authServer.getTotalResponsesServed());\n    Assert.assertEquals(4, registry.getTotalResponsesServed());\n\n    Mockito.verify(eventHandlers)\n        .dispatch(\n            logContains(\"server did not return 'WWW-Authenticate: Bearer' header. Actual: Basic\"));\n    Mockito.verify(eventHandlers)\n        .dispatch(\n            logContains(\"server did not return 'WWW-Authenticate: Bearer' header. Actual: null\"));\n  }\n\n  @Test\n  public void testAutomaticTokenRefresh_refreshLimit()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String tokenResponse = \"HTTP/1.1 200 OK\\nContent-Length: 26\\n\\n{\\\"token\\\":\\\"awesome-token!\\\"}\";\n    authServer = new TestWebServer(false, Arrays.asList(tokenResponse), 5);\n\n    String bearerAuth =\n        \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\nWWW-Authenticate: Bearer realm=\\\"\"\n            + authServer.getEndpoint()\n            + \"\\\"\\n\\n\";\n    String unauthorized = \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\n\\n\";\n    List<String> responses =\n        Arrays.asList(\n            bearerAuth, unauthorized, unauthorized, unauthorized, unauthorized, unauthorized);\n    registry = new TestWebServer(false, responses, responses.size(), true);\n\n    RegistryClient registryClient = createRegistryClient(null);\n    Assert.assertTrue(registryClient.doPushBearerAuth());\n\n    try {\n      registryClient.checkBlob(digest);\n      Assert.fail(\"Should have given up refreshing after 4 attempts\");\n    } catch (RegistryUnauthorizedException ex) {\n      Assert.assertEquals(401, ex.getHttpResponseException().getStatusCode());\n      Assert.assertEquals(5, authServer.getTotalResponsesServed());\n      // 1 response asking to do bearer auth + 4 unauth responses for 4 refresh attempts + 1 final\n      // unauth response propagated as RegistryUnauthorizedException here\n      Assert.assertEquals(6, registry.getTotalResponsesServed());\n    }\n  }\n\n  @Test\n  public void testConfigureBasicAuth()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String basicAuth = \"HTTP/1.1 200 OK\\nContent-Length: 56789\\n\\n\";\n    registry = new TestWebServer(false, Arrays.asList(basicAuth), 1);\n    RegistryClient registryClient = createRegistryClient(Credential.from(\"user\", \"pass\"));\n    registryClient.configureBasicAuth();\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(56789, digestAndSize.get().getSize());\n    MatcherAssert.assertThat(\n        registry.getInputRead(), CoreMatchers.containsString(\"Authorization: Basic dXNlcjpwYXNz\"));\n  }\n\n  @Test\n  public void testAuthPullByWwwAuthenticate_bearerAuth()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String tokenResponse = \"HTTP/1.1 200 OK\\nContent-Length: 26\\n\\n{\\\"token\\\":\\\"awesome-token!\\\"}\";\n    authServer = new TestWebServer(false, Arrays.asList(tokenResponse), 1);\n\n    String blobResponse = \"HTTP/1.1 200 OK\\nContent-Length: 5678\\n\\n\";\n    registry = new TestWebServer(false, Arrays.asList(blobResponse), 1);\n\n    RegistryClient registryClient = createRegistryClient(Credential.from(\"user\", \"pass\"));\n    registryClient.authPullByWwwAuthenticate(\"Bearer realm=\\\"\" + authServer.getEndpoint() + \"\\\"\");\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(5678, digestAndSize.get().getSize());\n\n    Mockito.verify(eventHandlers).dispatch(logContains(\"bearer auth succeeded\"));\n  }\n\n  @Test\n  public void testAuthPullByWwwAuthenticate_basicAuth()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String blobResponse = \"HTTP/1.1 200 OK\\nContent-Length: 5678\\n\\n\";\n    registry = new TestWebServer(false, Arrays.asList(blobResponse), 1);\n\n    RegistryClient registryClient = createRegistryClient(Credential.from(\"user\", \"pass\"));\n    registryClient.authPullByWwwAuthenticate(\"Basic foo\");\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(5678, digestAndSize.get().getSize());\n\n    MatcherAssert.assertThat(\n        registry.getInputRead(), CoreMatchers.containsString(\"Authorization: Basic dXNlcjpwYXNz\"));\n  }\n\n  @Test\n  public void testAuthPullByWwwAuthenticate_basicAuthRequestedButNullCredential()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String blobResponse = \"HTTP/1.1 200 OK\\nContent-Length: 5678\\n\\n\";\n    registry = new TestWebServer(false, Arrays.asList(blobResponse), 1);\n\n    RegistryClient registryClient = createRegistryClient(null);\n    registryClient.authPullByWwwAuthenticate(\"Basic foo\");\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(5678, digestAndSize.get().getSize());\n\n    MatcherAssert.assertThat(\n        registry.getInputRead(), CoreMatchers.not(CoreMatchers.containsString(\"Authorization:\")));\n  }\n\n  @Test\n  public void testAuthPullByWwwAuthenticate_basicAuthRequestedButOAuth2Credential()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String blobResponse = \"HTTP/1.1 200 OK\\nContent-Length: 5678\\n\\n\";\n    registry = new TestWebServer(false, Arrays.asList(blobResponse), 1);\n\n    Credential credential = Credential.from(Credential.OAUTH2_TOKEN_USER_NAME, \"pass\");\n    Assert.assertTrue(credential.isOAuth2RefreshToken());\n    RegistryClient registryClient = createRegistryClient(credential);\n    registryClient.authPullByWwwAuthenticate(\"Basic foo\");\n\n    Optional<BlobDescriptor> digestAndSize = registryClient.checkBlob(digest);\n    Assert.assertEquals(5678, digestAndSize.get().getSize());\n\n    MatcherAssert.assertThat(\n        registry.getInputRead(), CoreMatchers.not(CoreMatchers.containsString(\"Authorization:\")));\n  }\n\n  @Test\n  public void testAuthPullByWwwAuthenticate_invalidAuthMethod() {\n    RegistryClient registryClient =\n        RegistryClient.factory(eventHandlers, \"server\", \"foo/bar\", null).newRegistryClient();\n    try {\n      registryClient.authPullByWwwAuthenticate(\"invalid WWW-Authenticate\");\n      Assert.fail();\n    } catch (RegistryException ex) {\n      Assert.assertEquals(\n          \"Failed to authenticate with registry server/foo/bar because: 'Bearer' was not found in \"\n              + \"the 'WWW-Authenticate' header, tried to parse: invalid WWW-Authenticate\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testPullManifest()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String manifestResponse =\n        \"HTTP/1.1 200 OK\\nContent-Length: 307\\n\\n{\\n\"\n            + \"    \\\"schemaVersion\\\": 2,\\n\"\n            + \"    \\\"mediaType\\\": \\\"application/vnd.docker.distribution.manifest.v2+json\\\",\\n\"\n            + \"    \\\"config\\\": {\\n\"\n            + \"        \\\"mediaType\\\": \\\"application/vnd.docker.container.image.v1+json\\\",\\n\"\n            + \"        \\\"size\\\": 7023,\\n\"\n            + \"        \\\"digest\\\": \\\"sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\\\"\\n\"\n            + \"    }\\n\"\n            + \"}\";\n\n    registry = new TestWebServer(false, Arrays.asList(manifestResponse), 1);\n    RegistryClient registryClient = createRegistryClient(null);\n    ManifestAndDigest<?> manifestAndDigest = registryClient.pullManifest(\"image-tag\");\n\n    Assert.assertEquals(\n        \"sha256:6b61466eabab6e5ffb68ae2bd9b85c789225540c2ac54ea1f71eb327588e8946\",\n        manifestAndDigest.getDigest().toString());\n\n    Assert.assertTrue(manifestAndDigest.getManifest() instanceof V22ManifestTemplate);\n    V22ManifestTemplate manifest = (V22ManifestTemplate) manifestAndDigest.getManifest();\n    Assert.assertEquals(2, manifest.getSchemaVersion());\n    Assert.assertEquals(\n        \"application/vnd.docker.distribution.manifest.v2+json\", manifest.getManifestMediaType());\n    Assert.assertEquals(\n        \"sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\",\n        manifest.getContainerConfiguration().getDigest().toString());\n    Assert.assertEquals(7023, manifest.getContainerConfiguration().getSize());\n\n    MatcherAssert.assertThat(\n        registry.getInputRead(),\n        CoreMatchers.containsString(\"GET /v2/foo/bar/manifests/image-tag \"));\n  }\n\n  @Test\n  public void testPullManifest_manifestList()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,\n          RegistryException {\n    String manifestResponse =\n        \"HTTP/1.1 200 OK\\nContent-Length: 403\\n\\n{\\n\"\n            + \"  \\\"schemaVersion\\\": 2,\\n\"\n            + \"  \\\"mediaType\\\": \\\"application/vnd.docker.distribution.manifest.list.v2+json\\\",\\n\"\n            + \"  \\\"manifests\\\": [\\n\"\n            + \"    {\\n\"\n            + \"      \\\"mediaType\\\": \\\"application/vnd.docker.distribution.manifest.v2+json\\\",\\n\"\n            + \"      \\\"size\\\": 7143,\\n\"\n            + \"      \\\"digest\\\": \\\"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\\\",\\n\"\n            + \"      \\\"platform\\\": {\\n\"\n            + \"        \\\"architecture\\\": \\\"amd64\\\",\\n\"\n            + \"        \\\"os\\\": \\\"linux\\\"\\n\"\n            + \"      }\\n\"\n            + \"    }\\n\"\n            + \"  ]\\n\"\n            + \"}\";\n\n    registry = new TestWebServer(false, Arrays.asList(manifestResponse), 1);\n    RegistryClient registryClient = createRegistryClient(null);\n    ManifestAndDigest<?> manifestAndDigest = registryClient.pullManifest(\"image-tag\");\n\n    Assert.assertEquals(\n        \"sha256:a340fa38667f765f8cfd79d4bc684ec8a6f48cdd63abfe36e109f4125ee38488\",\n        manifestAndDigest.getDigest().toString());\n\n    Assert.assertTrue(manifestAndDigest.getManifest() instanceof V22ManifestListTemplate);\n    V22ManifestListTemplate manifestList =\n        (V22ManifestListTemplate) manifestAndDigest.getManifest();\n    Assert.assertEquals(2, manifestList.getSchemaVersion());\n    Assert.assertEquals(\n        Arrays.asList(\"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\"),\n        manifestList.getDigestsForPlatform(\"amd64\", \"linux\"));\n\n    MatcherAssert.assertThat(\n        registry.getInputRead(),\n        CoreMatchers.containsString(\"GET /v2/foo/bar/manifests/image-tag \"));\n  }\n\n  /**\n   * Sets up an auth server and a registry. The auth server can return a bearer token up to {@code\n   * maxAuthTokens} times. The registry will initially return \"401 Unauthorized\" for {@code\n   * maxTokenResponses} times. (Therefore, a registry client has to get auth tokens from the auth\n   * server {@code maxAuthTokens} times. After that, the registry returns {@code finalResponse}.\n   */\n  private void setUpAuthServerAndRegistry(int maxAuthTokens, @Nullable String finalResponse)\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    String tokenResponse = \"HTTP/1.1 200 OK\\nContent-Length: 26\\n\\n{\\\"token\\\":\\\"awesome-token!\\\"}\";\n    authServer = new TestWebServer(false, Arrays.asList(tokenResponse), maxAuthTokens);\n\n    String bearerAuth =\n        \"HTTP/1.1 401 Unauthorized\\nContent-Length: 0\\nWWW-Authenticate: Bearer realm=\\\"\"\n            + authServer.getEndpoint()\n            + \"\\\"\\n\\n\";\n    List<String> responses = new ArrayList<>(Collections.nCopies(maxAuthTokens, bearerAuth));\n    if (finalResponse != null) {\n      responses.add(finalResponse);\n    }\n\n    registry = new TestWebServer(false, responses, responses.size(), true);\n  }\n\n  private RegistryClient createRegistryClient(@Nullable Credential credential) {\n    return RegistryClient.factory(\n            eventHandlers, \"localhost:\" + registry.getLocalPort(), \"foo/bar\", new PlainHttpClient())\n        .setCredential(credential)\n        .newRegistryClient();\n  }\n\n  private LogEvent logContains(String substring) {\n    ArgumentMatcher<LogEvent> matcher = event -> event.getMessage().contains(substring);\n    return ArgumentMatchers.argThat(matcher);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryEndpointCallerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.InsecureRegistryException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.http.Authorization;\nimport com.google.cloud.tools.jib.http.BlobHttpContent;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Request;\nimport com.google.cloud.tools.jib.http.RequestWrapper;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.common.io.CharStreams;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.MalformedURLException;\nimport java.net.SocketException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLPeerUnverifiedException;\nimport org.apache.http.NoHttpResponseException;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.runner.RunWith;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link RegistryEndpointCaller}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class RegistryEndpointCallerTest {\n\n  /** Implementation of {@link RegistryEndpointProvider} for testing. */\n  private static class TestRegistryEndpointProvider implements RegistryEndpointProvider<String> {\n\n    @Override\n    public String getHttpMethod() {\n      return \"httpMethod\";\n    }\n\n    @Override\n    public URL getApiRoute(String apiRouteBase) throws MalformedURLException {\n      return new URL(apiRouteBase + \"api\");\n    }\n\n    @Nullable\n    @Override\n    public BlobHttpContent getContent() {\n      return null;\n    }\n\n    @Override\n    public List<String> getAccept() {\n      return Collections.emptyList();\n    }\n\n    @Nullable\n    @Override\n    public String handleResponse(Response response) throws IOException {\n      return CharStreams.toString(\n          new InputStreamReader(response.getBody(), StandardCharsets.UTF_8));\n    }\n\n    @Override\n    public String getActionDescription() {\n      return \"actionDescription\";\n    }\n\n    @Override\n    public String handleHttpResponseException(ResponseException responseException)\n        throws ResponseException, RegistryErrorException {\n      throw responseException;\n    }\n  }\n\n  private static ResponseException mockResponseException(int statusCode) {\n    ResponseException mock = Mockito.mock(ResponseException.class);\n    Mockito.when(mock.getStatusCode()).thenReturn(statusCode);\n    return mock;\n  }\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  @Mock private EventHandlers mockEventHandlers;\n  @Mock private FailoverHttpClient mockHttpClient;\n  @Mock private Response mockResponse;\n\n  private RegistryEndpointCaller<String> endpointCaller;\n\n  @Before\n  public void setUp() throws IOException {\n    endpointCaller =\n        new RegistryEndpointCaller<>(\n            mockEventHandlers,\n            \"userAgent\",\n            new TestRegistryEndpointProvider(),\n            Authorization.fromBasicCredentials(\"user\", \"pass\"),\n            new RegistryEndpointRequestProperties(\"serverUrl\", \"imageName\"),\n            mockHttpClient);\n\n    Mockito.when(mockResponse.getBody())\n        .thenReturn(new ByteArrayInputStream(\"body\".getBytes(StandardCharsets.UTF_8)));\n  }\n\n  @Test\n  public void testCall_secureCallerOnUnverifiableServer() throws IOException, RegistryException {\n    Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), Mockito.any()))\n        .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class));\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Should throw InsecureRegistryException when getting SSLException\");\n    } catch (InsecureRegistryException ex) {\n      Assert.assertEquals(\n          \"Failed to verify the server at https://serverUrl/v2/api because only secure connections \"\n              + \"are allowed.\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCall_noHttpResponse() throws IOException, RegistryException {\n    NoHttpResponseException mockNoResponseException = Mockito.mock(NoHttpResponseException.class);\n    setUpRegistryResponse(mockNoResponseException);\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Call should have failed\");\n\n    } catch (NoHttpResponseException ex) {\n      Assert.assertSame(mockNoResponseException, ex);\n    }\n  }\n\n  @Test\n  public void testCall_unauthorized() throws IOException, RegistryException {\n    verifyThrowsRegistryUnauthorizedException(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);\n  }\n\n  @Test\n  public void testCall_credentialsNotSentOverHttp() throws IOException, RegistryException {\n    ResponseException unauthorizedException =\n        mockResponseException(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);\n    Mockito.when(unauthorizedException.requestAuthorizationCleared()).thenReturn(true);\n    setUpRegistryResponse(unauthorizedException);\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Call should have failed\");\n\n    } catch (RegistryCredentialsNotSentException ex) {\n      Assert.assertEquals(\n          \"Required credentials for serverUrl/imageName were not sent because the connection was over HTTP\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCall_credentialsForcedOverHttp() throws IOException, RegistryException {\n    ResponseException unauthorizedException =\n        mockResponseException(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);\n    setUpRegistryResponse(unauthorizedException);\n    System.setProperty(JibSystemProperties.SEND_CREDENTIALS_OVER_HTTP, \"true\");\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Call should have failed\");\n\n    } catch (RegistryCredentialsNotSentException ex) {\n      throw new AssertionError(\"should have sent credentials\", ex);\n    } catch (RegistryUnauthorizedException ex) {\n      Assert.assertEquals(\"Unauthorized for serverUrl/imageName\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testCall_forbidden() throws IOException, RegistryException {\n    verifyThrowsRegistryUnauthorizedException(HttpStatusCodes.STATUS_CODE_FORBIDDEN);\n  }\n\n  @Test\n  public void testCall_badRequest() throws IOException, RegistryException {\n    verifyThrowsRegistryErrorException(HttpStatusCodes.STATUS_CODE_BAD_REQUEST);\n  }\n\n  @Test\n  public void testCall_notFound() throws IOException, RegistryException {\n    verifyThrowsRegistryErrorException(HttpStatusCodes.STATUS_CODE_NOT_FOUND);\n  }\n\n  @Test\n  public void testCall_methodNotAllowed() throws IOException, RegistryException {\n    verifyThrowsRegistryErrorException(HttpStatusCodes.STATUS_CODE_METHOD_NOT_ALLOWED);\n  }\n\n  @Test\n  public void testCall_unknown() throws IOException, RegistryException {\n    ResponseException responseException =\n        mockResponseException(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);\n    setUpRegistryResponse(responseException);\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Call should have failed\");\n\n    } catch (ResponseException ex) {\n      Assert.assertSame(responseException, ex);\n    }\n  }\n\n  @Test\n  public void testCall_logErrorOnIoExceptions() throws IOException, RegistryException {\n    IOException ioException = new IOException(\"detailed exception message\");\n    setUpRegistryResponse(ioException);\n\n    try {\n      endpointCaller.call();\n      Assert.fail();\n\n    } catch (IOException ex) {\n      Assert.assertSame(ioException, ex);\n      Mockito.verify(mockEventHandlers)\n          .dispatch(\n              LogEvent.error(\"\\u001B[31;1mI/O error for image [serverUrl/imageName]:\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(LogEvent.error(\"\\u001B[31;1m    java.io.IOException\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(LogEvent.error(\"\\u001B[31;1m    detailed exception message\\u001B[0m\"));\n      Mockito.verifyNoMoreInteractions(mockEventHandlers);\n    }\n  }\n\n  @Test\n  public void testCall_logErrorOnBrokenPipe() throws IOException, RegistryException {\n    IOException ioException = new IOException(\"this is due to broken pipe\");\n    setUpRegistryResponse(ioException);\n\n    try {\n      endpointCaller.call();\n      Assert.fail();\n\n    } catch (IOException ex) {\n      Assert.assertSame(ioException, ex);\n      Mockito.verify(mockEventHandlers)\n          .dispatch(\n              LogEvent.error(\"\\u001B[31;1mI/O error for image [serverUrl/imageName]:\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(LogEvent.error(\"\\u001B[31;1m    java.io.IOException\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(LogEvent.error(\"\\u001B[31;1m    this is due to broken pipe\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(\n              LogEvent.error(\n                  \"\\u001B[31;1mbroken pipe: the server shut down the connection. Check the server \"\n                      + \"log if possible. This could also be a proxy issue. For example, a proxy \"\n                      + \"may prevent sending packets that are too large.\\u001B[0m\"));\n      Mockito.verifyNoMoreInteractions(mockEventHandlers);\n    }\n  }\n\n  @Test\n  public void testCall_logNullExceptionMessage() throws IOException, RegistryException {\n    setUpRegistryResponse(new IOException());\n\n    try {\n      endpointCaller.call();\n      Assert.fail();\n\n    } catch (IOException ex) {\n      Mockito.verify(mockEventHandlers)\n          .dispatch(\n              LogEvent.error(\"\\u001B[31;1mI/O error for image [serverUrl/imageName]:\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(LogEvent.error(\"\\u001B[31;1m    java.io.IOException\\u001B[0m\"));\n      Mockito.verify(mockEventHandlers)\n          .dispatch(LogEvent.error(\"\\u001B[31;1m    (null exception message)\\u001B[0m\"));\n      Mockito.verifyNoMoreInteractions(mockEventHandlers);\n    }\n  }\n\n  @Test\n  public void testHttpTimeout_propertyNotSet() throws IOException, RegistryException {\n    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);\n    Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))\n        .thenReturn(mockResponse);\n\n    System.clearProperty(JibSystemProperties.HTTP_TIMEOUT);\n    endpointCaller.call();\n\n    // We fall back to the default timeout:\n    // https://github.com/GoogleContainerTools/jib/pull/656#discussion_r203562639\n    Assert.assertEquals(20000, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());\n  }\n\n  @Test\n  public void testHttpTimeout_stringValue() throws IOException, RegistryException {\n    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);\n    Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))\n        .thenReturn(mockResponse);\n\n    System.setProperty(JibSystemProperties.HTTP_TIMEOUT, \"random string\");\n    endpointCaller.call();\n\n    Assert.assertEquals(20000, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());\n  }\n\n  @Test\n  public void testHttpTimeout_negativeValue() throws IOException, RegistryException {\n    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);\n    Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))\n        .thenReturn(mockResponse);\n\n    System.setProperty(JibSystemProperties.HTTP_TIMEOUT, \"-1\");\n    endpointCaller.call();\n\n    // We let the negative value pass through:\n    // https://github.com/GoogleContainerTools/jib/pull/656#discussion_r203562639\n    Assert.assertEquals(-1, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());\n  }\n\n  @Test\n  public void testHttpTimeout_0accepted() throws IOException, RegistryException {\n    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);\n    Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))\n        .thenReturn(mockResponse);\n\n    System.setProperty(JibSystemProperties.HTTP_TIMEOUT, \"0\");\n    endpointCaller.call();\n\n    Assert.assertEquals(0, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());\n  }\n\n  @Test\n  public void testHttpTimeout() throws IOException, RegistryException {\n    ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);\n    Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))\n        .thenReturn(mockResponse);\n\n    System.setProperty(JibSystemProperties.HTTP_TIMEOUT, \"7593\");\n    endpointCaller.call();\n\n    Assert.assertEquals(7593, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());\n  }\n\n  @Test\n  public void testIsBrokenPipe_notBrokenPipe() {\n    Assert.assertFalse(RegistryEndpointCaller.isBrokenPipe(new IOException()));\n    Assert.assertFalse(RegistryEndpointCaller.isBrokenPipe(new SocketException()));\n    Assert.assertFalse(RegistryEndpointCaller.isBrokenPipe(new SSLException(\"mock\")));\n  }\n\n  @Test\n  public void testIsBrokenPipe_brokenPipe() {\n    Assert.assertTrue(RegistryEndpointCaller.isBrokenPipe(new IOException(\"cool broken pipe !\")));\n    Assert.assertTrue(RegistryEndpointCaller.isBrokenPipe(new SocketException(\"BROKEN PIPE\")));\n    Assert.assertTrue(RegistryEndpointCaller.isBrokenPipe(new SSLException(\"calm BrOkEn PiPe\")));\n  }\n\n  @Test\n  public void testIsBrokenPipe_nestedBrokenPipe() {\n    IOException exception = new IOException(new SSLException(new SocketException(\"Broken pipe\")));\n    Assert.assertTrue(RegistryEndpointCaller.isBrokenPipe(exception));\n  }\n\n  @Test\n  public void testIsBrokenPipe_terminatesWhenCauseIsOriginal() {\n    IOException exception = Mockito.mock(IOException.class);\n    Mockito.when(exception.getCause()).thenReturn(exception);\n\n    Assert.assertFalse(RegistryEndpointCaller.isBrokenPipe(exception));\n  }\n\n  @Test\n  public void testNewRegistryErrorException_jsonErrorOutput() {\n    ResponseException httpException = Mockito.mock(ResponseException.class);\n    Mockito.when(httpException.getContent())\n        .thenReturn(\n            \"{\\\"errors\\\": [{\\\"code\\\": \\\"MANIFEST_UNKNOWN\\\", \\\"message\\\": \\\"manifest unknown\\\"}]}\");\n\n    RegistryErrorException registryException =\n        endpointCaller.newRegistryErrorException(httpException);\n    Assert.assertSame(httpException, registryException.getCause());\n    Assert.assertEquals(\n        \"Tried to actionDescription but failed because: manifest unknown\",\n        registryException.getMessage());\n  }\n\n  @Test\n  public void testNewRegistryErrorException_nonJsonErrorOutput() {\n    ResponseException httpException = Mockito.mock(ResponseException.class);\n    // Registry returning non-structured error output\n    Mockito.when(httpException.getContent()).thenReturn(\">>>>> (404) page not found <<<<<\");\n    Mockito.when(httpException.getStatusCode()).thenReturn(404);\n\n    RegistryErrorException registryException =\n        endpointCaller.newRegistryErrorException(httpException);\n    Assert.assertSame(httpException, registryException.getCause());\n    Assert.assertEquals(\n        \"Tried to actionDescription but failed because: registry returned error code 404; \"\n            + \"possible causes include invalid or wrong reference. Actual error output follows:\\n\"\n            + \">>>>> (404) page not found <<<<<\\n\",\n        registryException.getMessage());\n  }\n\n  @Test\n  public void testNewRegistryErrorException_noOutputFromRegistry() {\n    ResponseException httpException = Mockito.mock(ResponseException.class);\n    // Registry returning null error output\n    Mockito.when(httpException.getContent()).thenReturn(null);\n    Mockito.when(httpException.getStatusCode()).thenReturn(404);\n\n    RegistryErrorException registryException =\n        endpointCaller.newRegistryErrorException(httpException);\n    Assert.assertSame(httpException, registryException.getCause());\n    Assert.assertEquals(\n        \"Tried to actionDescription but failed because: registry returned error code 404 \"\n            + \"but did not return any details; possible causes include invalid or wrong reference, or proxy/firewall/VPN interfering \\n\",\n        registryException.getMessage());\n  }\n\n  /**\n   * Verifies that a response with {@code httpStatusCode} throws {@link\n   * RegistryUnauthorizedException}.\n   */\n  private void verifyThrowsRegistryUnauthorizedException(int httpStatusCode)\n      throws IOException, RegistryException {\n    ResponseException responseException = mockResponseException(httpStatusCode);\n    setUpRegistryResponse(responseException);\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Call should have failed\");\n\n    } catch (RegistryUnauthorizedException ex) {\n      Assert.assertEquals(\"serverUrl/imageName\", ex.getImageReference());\n      Assert.assertSame(responseException, ex.getCause());\n    }\n  }\n\n  /**\n   * Verifies that a response with {@code httpStatusCode} throws {@link\n   * RegistryUnauthorizedException}.\n   */\n  private void verifyThrowsRegistryErrorException(int httpStatusCode)\n      throws IOException, RegistryException {\n    ResponseException errorResponse = mockResponseException(httpStatusCode);\n    Mockito.when(errorResponse.getContent())\n        .thenReturn(\"{\\\"errors\\\":[{\\\"code\\\":\\\"code\\\",\\\"message\\\":\\\"message\\\"}]}\");\n    setUpRegistryResponse(errorResponse);\n\n    try {\n      endpointCaller.call();\n      Assert.fail(\"Call should have failed\");\n\n    } catch (RegistryErrorException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\n              \"Tried to actionDescription but failed because: unknown error code: code (message)\"));\n    }\n  }\n\n  private void setUpRegistryResponse(Exception exceptionToThrow)\n      throws MalformedURLException, IOException {\n    Mockito.when(\n            mockHttpClient.call(\n                Mockito.eq(\"httpMethod\"),\n                Mockito.eq(new URL(\"https://serverUrl/v2/api\")),\n                Mockito.any()))\n        .thenThrow(exceptionToThrow);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryErrorExceptionBuilderTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry;\n\nimport com.google.api.client.http.HttpResponseException;\nimport com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link RegistryErrorExceptionBuilder}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class RegistryErrorExceptionBuilderTest {\n\n  @Mock private HttpResponseException mockHttpResponseException;\n\n  @Test\n  public void testAddErrorEntry() {\n    RegistryErrorExceptionBuilder builder =\n        new RegistryErrorExceptionBuilder(\"do something\", mockHttpResponseException)\n            .addReason(\n                new ErrorEntryTemplate(ErrorCodes.MANIFEST_INVALID.name(), \"manifest invalid\"))\n            .addReason(new ErrorEntryTemplate(ErrorCodes.BLOB_UNKNOWN.name(), \"blob unknown\"))\n            .addReason(\n                new ErrorEntryTemplate(ErrorCodes.MANIFEST_UNKNOWN.name(), \"manifest unknown\"))\n            .addReason(new ErrorEntryTemplate(ErrorCodes.TAG_INVALID.name(), \"tag invalid\"))\n            .addReason(\n                new ErrorEntryTemplate(\n                    ErrorCodes.MANIFEST_UNVERIFIED.name(), \"manifest unverified\"))\n            .addReason(\n                new ErrorEntryTemplate(ErrorCodes.UNSUPPORTED.name(), \"some other error happened\"))\n            .addReason(new ErrorEntryTemplate(\"unknown\", \"some unknown error happened\"));\n\n    try {\n      throw builder.build();\n\n    } catch (RegistryErrorException ex) {\n      Assert.assertEquals(\n          \"Tried to do something but failed because: manifest invalid (something went wrong), blob \"\n              + \"unknown (something went wrong), manifest unknown, tag invalid, manifest \"\n              + \"unverified, other: some other error happened, unknown error code: unknown (some \"\n              + \"unknown error happened)\",\n          ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetrieverTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link DockerConfigCredentialRetriever}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class DockerConfigCredentialRetrieverTest {\n\n  private static final Credential FAKE_CREDENTIAL = Credential.from(\"username\", \"password\");\n\n  @Mock private DockerCredentialHelper mockDockerCredentialHelper;\n  @Mock private DockerConfig mockDockerConfig;\n  @Mock private Consumer<LogEvent> mockLogger;\n\n  private Path dockerConfigFile;\n\n  @Before\n  public void setUp()\n      throws URISyntaxException, CredentialHelperUnhandledServerUrlException,\n          CredentialHelperNotFoundException, IOException {\n    dockerConfigFile = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n    Mockito.when(mockDockerCredentialHelper.retrieve()).thenReturn(FAKE_CREDENTIAL);\n  }\n\n  @Test\n  public void testRetrieve_nonExistentDockerConfigFile() throws IOException {\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"some registry\", Paths.get(\"fake/path\"));\n\n    Assert.assertFalse(dockerConfigCredentialRetriever.retrieve(mockLogger).isPresent());\n  }\n\n  @Test\n  public void testRetrieve_hasAuth() throws IOException {\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"some other registry\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertTrue(credentials.isPresent());\n    Assert.assertEquals(\"some\", credentials.get().getUsername());\n    Assert.assertEquals(\"other:auth\", credentials.get().getPassword());\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.info(\n                \"Docker config auths section defines credentials for some other registry\"));\n  }\n\n  @Test\n  public void testRetrieve_authTakesPrecedenceOverUsernameAndPassword() throws IOException {\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"auth and userpw registry\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertTrue(credentials.isPresent());\n    Assert.assertEquals(\"some\", credentials.get().getUsername());\n    Assert.assertEquals(\"auth\", credentials.get().getPassword());\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.info(\n                \"Docker config auths section defines credentials for auth and userpw registry\"));\n  }\n\n  @Test\n  public void testRetrieve_hasAuthWithUsernameAndPassword() throws IOException {\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"userpw registry\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertTrue(credentials.isPresent());\n    Assert.assertEquals(\"someuser\", credentials.get().getUsername());\n    Assert.assertEquals(\"somepw\", credentials.get().getPassword());\n    Mockito.verify(mockLogger)\n        .accept(\n            LogEvent.info(\n                \"Docker config auths section defines username and password for userpw registry\"));\n  }\n\n  @Test\n  public void testRetrieve_hasAuth_legacyConfigFormat() throws IOException, URISyntaxException {\n    dockerConfigFile = Paths.get(Resources.getResource(\"core/json/legacy_dockercfg\").toURI());\n\n    DockerConfigCredentialRetriever retriever1 =\n        DockerConfigCredentialRetriever.createForLegacyFormat(\"some registry\", dockerConfigFile);\n    Optional<Credential> credentials1 = retriever1.retrieve(mockLogger);\n    Assert.assertEquals(\"some\", credentials1.get().getUsername());\n    Assert.assertEquals(\"other:auth\", credentials1.get().getPassword());\n\n    DockerConfigCredentialRetriever retriever2 =\n        DockerConfigCredentialRetriever.createForLegacyFormat(\"example.com\", dockerConfigFile);\n    Optional<Credential> credentials2 = retriever2.retrieve(mockLogger);\n    Assert.assertEquals(\"user\", credentials2.get().getUsername());\n    Assert.assertEquals(\"pass\", credentials2.get().getPassword());\n\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.info(\"Docker config auths section defines credentials for some registry\"));\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.info(\"Docker config auths section defines credentials for example.com\"));\n  }\n\n  @Test\n  public void testRetrieve_credentialHelperTakesPrecedenceOverAuth() {\n    Mockito.when(mockDockerConfig.getCredentialHelperFor(\"some registry\"))\n        .thenReturn(mockDockerCredentialHelper);\n    Mockito.when(mockDockerCredentialHelper.getCredentialHelper())\n        .thenReturn(Paths.get(\"docker-credential-foo\"));\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"some registry\", dockerConfigFile);\n\n    Assert.assertEquals(\n        Optional.of(FAKE_CREDENTIAL),\n        dockerConfigCredentialRetriever.retrieve(mockDockerConfig, mockLogger));\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.info(\"trying docker-credential-foo for some registry\"));\n  }\n\n  @Test\n  public void testRetrieve_credentialHelper_warn()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    Mockito.when(mockDockerConfig.getCredentialHelperFor(\"another registry\"))\n        .thenReturn(mockDockerCredentialHelper);\n    Mockito.when(mockDockerCredentialHelper.retrieve())\n        .thenThrow(\n            new CredentialHelperNotFoundException(\n                Paths.get(\"docker-credential-path\"), new Throwable(\"cause\")));\n\n    DockerConfigCredentialRetriever.create(\"another registry\", dockerConfigFile)\n        .retrieve(mockDockerConfig, mockLogger);\n\n    Mockito.verify(mockLogger)\n        .accept(LogEvent.warn(\"The system does not have docker-credential-path CLI\"));\n    Mockito.verify(mockLogger).accept(LogEvent.warn(\"  Caused by: cause\"));\n  }\n\n  @Test\n  public void testRetrieve_none() throws IOException {\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"unknown registry\", dockerConfigFile);\n\n    Assert.assertFalse(dockerConfigCredentialRetriever.retrieve(mockLogger).isPresent());\n  }\n\n  @Test\n  public void testRetrieve_credentialFromAlias() {\n    Mockito.when(mockDockerConfig.getCredentialHelperFor(\"registry.hub.docker.com\"))\n        .thenReturn(mockDockerCredentialHelper);\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"registry.hub.docker.com\", dockerConfigFile);\n\n    Assert.assertEquals(\n        Optional.of(FAKE_CREDENTIAL),\n        dockerConfigCredentialRetriever.retrieve(mockDockerConfig, mockLogger));\n  }\n\n  @Test\n  public void testRetrieve_suffixMatching() throws IOException, URISyntaxException {\n    Path dockerConfigFile =\n        Paths.get(Resources.getResource(\"core/json/dockerconfig_index_docker_io_v1.json\").toURI());\n\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"index.docker.io\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertTrue(credentials.isPresent());\n    Assert.assertEquals(\"token for\", credentials.get().getUsername());\n    Assert.assertEquals(\" index.docker.io/v1/\", credentials.get().getPassword());\n  }\n\n  @Test\n  public void testRetrieve_suffixMatchingFromAlias() throws IOException, URISyntaxException {\n    Path dockerConfigFile =\n        Paths.get(Resources.getResource(\"core/json/dockerconfig_index_docker_io_v1.json\").toURI());\n\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"registry.hub.docker.com\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertTrue(credentials.isPresent());\n    Assert.assertEquals(\"token for\", credentials.get().getUsername());\n    Assert.assertEquals(\" index.docker.io/v1/\", credentials.get().getPassword());\n  }\n\n  @Test\n  public void testRetrieve_azureIdentityToken() throws IOException, URISyntaxException {\n    Path dockerConfigFile =\n        Paths.get(Resources.getResource(\"core/json/dockerconfig_identity_token.json\").toURI());\n\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"some registry\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertTrue(credentials.isPresent());\n    Assert.assertEquals(\"<token>\", credentials.get().getUsername());\n    Assert.assertEquals(\"cool identity token\", credentials.get().getPassword());\n  }\n\n  @Test\n  public void testRetrieve_noErrorWhenMissingAuthField() throws IOException, URISyntaxException {\n    Path dockerConfigFile = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    DockerConfigCredentialRetriever dockerConfigCredentialRetriever =\n        DockerConfigCredentialRetriever.create(\"no auth field\", dockerConfigFile);\n\n    Optional<Credential> credentials = dockerConfigCredentialRetriever.retrieve(mockLogger);\n    Assert.assertFalse(credentials.isPresent());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.credentials.json.DockerConfigTemplate;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Base64;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link DockerConfig}. */\npublic class DockerConfigTest {\n\n  private static String decodeBase64(String base64String) {\n    return new String(Base64.getDecoder().decode(base64String), StandardCharsets.UTF_8);\n  }\n\n  @Test\n  public void test_fromJson() throws URISyntaxException, IOException {\n    // Loads the JSON string.\n    Path jsonFile = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    // Deserializes into a docker config JSON object.\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(jsonFile, DockerConfigTemplate.class));\n\n    Assert.assertEquals(\n        \"some:auth\", decodeBase64(dockerConfig.getAuthFor(\"some registry\").getAuth()));\n    Assert.assertEquals(\n        \"some:other:auth\", decodeBase64(dockerConfig.getAuthFor(\"some other registry\").getAuth()));\n    Assert.assertEquals(\"token\", decodeBase64(dockerConfig.getAuthFor(\"registry\").getAuth()));\n    Assert.assertEquals(\n        \"token\", decodeBase64(dockerConfig.getAuthFor(\"https://registry\").getAuth()));\n    Assert.assertNull(dockerConfig.getAuthFor(\"just registry\"));\n\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-some credential helper\"),\n        dockerConfig.getCredentialHelperFor(\"some registry\").getCredentialHelper());\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-another credential helper\"),\n        dockerConfig.getCredentialHelperFor(\"another registry\").getCredentialHelper());\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-some credential store\"),\n        dockerConfig.getCredentialHelperFor(\"unknown registry\").getCredentialHelper());\n  }\n\n  @Test\n  public void testGetAuthFor_orderOfMatchPreference() throws URISyntaxException, IOException {\n    Path json =\n        Paths.get(Resources.getResource(\"core/json/dockerconfig_extra_matches.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    Assert.assertEquals(\n        \"my-registry: exact match\", dockerConfig.getAuthFor(\"my-registry\").getAuth());\n    Assert.assertEquals(\n        \"cool-registry: with https\", dockerConfig.getAuthFor(\"cool-registry\").getAuth());\n    Assert.assertEquals(\n        \"awesome-registry: starting with name\",\n        dockerConfig.getAuthFor(\"awesome-registry\").getAuth());\n    Assert.assertEquals(\n        \"dull-registry: starting with name and with https\",\n        dockerConfig.getAuthFor(\"dull-registry\").getAuth());\n  }\n\n  @Test\n  public void testGetAuthFor_correctSuffixMatching() throws URISyntaxException, IOException {\n    Path json =\n        Paths.get(Resources.getResource(\"core/json/dockerconfig_extra_matches.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    Assert.assertNull(dockerConfig.getAuthFor(\"example\"));\n  }\n\n  @Test\n  public void testGetCredentialHelperFor_exactMatchInCredHelpers()\n      throws URISyntaxException, IOException {\n    Path json = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-credHelper for just.registry.in.helpers\"),\n        dockerConfig.getCredentialHelperFor(\"just.registry.in.helpers\").getCredentialHelper());\n  }\n\n  @Test\n  public void testGetCredentialHelperFor_withHttps() throws URISyntaxException, IOException {\n    Path json = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-credHelper for https__with.protocol.in.helpers\"),\n        dockerConfig.getCredentialHelperFor(\"with.protocol.in.helpers\").getCredentialHelper());\n  }\n\n  @Test\n  public void testGetCredentialHelperFor_withSuffix() throws URISyntaxException, IOException {\n    Path json = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-credHelper for with.suffix.in.helpers/v2/\"),\n        dockerConfig.getCredentialHelperFor(\"with.suffix.in.helpers\").getCredentialHelper());\n  }\n\n  @Test\n  public void testGetCredentialHelperFor_withProtocolAndSuffix()\n      throws URISyntaxException, IOException {\n    Path json = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    Assert.assertEquals(\n        Paths.get(\n            \"docker-credential-credHelper for https__with.protocol.and.suffix.in.helpers/suffix\"),\n        dockerConfig\n            .getCredentialHelperFor(\"with.protocol.and.suffix.in.helpers\")\n            .getCredentialHelper());\n  }\n\n  @Test\n  public void testGetCredentialHelperFor_correctSuffixMatching()\n      throws URISyntaxException, IOException {\n    Path json = Paths.get(Resources.getResource(\"core/json/dockerconfig.json\").toURI());\n\n    DockerConfig dockerConfig =\n        new DockerConfig(JsonTemplateMapper.readJsonFromFile(json, DockerConfigTemplate.class));\n\n    // Should fall back to credsStore\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-some credential store\"),\n        dockerConfig.getCredentialHelperFor(\"example\").getCredentialHelper());\n    Assert.assertEquals(\n        Paths.get(\"docker-credential-some credential store\"),\n        dockerConfig.getCredentialHelperFor(\"another.example\").getCredentialHelper());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelperTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.registry.credentials;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.ByteStreams;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.function.Function;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n@RunWith(MockitoJUnitRunner.class)\npublic class DockerCredentialHelperTest {\n\n  private static final String CREDENTIAL_JSON =\n      \"{\\\"Username\\\":\\\"myusername\\\",\\\"Secret\\\":\\\"mysecret\\\"}\";\n\n  @Mock private Process process;\n  @Mock private Function<List<String>, ProcessBuilder> processBuilderFactory;\n  @Mock private ProcessBuilder processBuilder;\n  @Mock private ProcessBuilder errorProcessBuilder;\n\n  private final Properties systemProperties = new Properties();\n\n  @Before\n  public void setUp() throws IOException {\n    systemProperties.put(\"os.name\", \"unknown\");\n\n    Mockito.when(process.getInputStream())\n        .thenReturn(new ByteArrayInputStream(CREDENTIAL_JSON.getBytes(StandardCharsets.UTF_8)));\n    Mockito.when(process.getOutputStream()).thenReturn(ByteStreams.nullOutputStream());\n    Mockito.when(processBuilder.start()).thenReturn(process);\n    Mockito.when(errorProcessBuilder.start())\n        .thenThrow(new IOException(\"No such file or directory\"));\n  }\n\n  @Test\n  public void testDockerCredentialsTemplate_read() throws IOException {\n    DockerCredentialHelper.DockerCredentialsTemplate template =\n        JsonTemplateMapper.readJson(\n            CREDENTIAL_JSON, DockerCredentialHelper.DockerCredentialsTemplate.class);\n    Assert.assertEquals(\"myusername\", template.username);\n    Assert.assertEquals(\"mysecret\", template.secret);\n  }\n\n  @Test\n  public void testDockerCredentialsTemplate_canReadNull() throws IOException {\n    String input = \"{}\";\n    DockerCredentialHelper.DockerCredentialsTemplate template =\n        JsonTemplateMapper.readJson(input, DockerCredentialHelper.DockerCredentialsTemplate.class);\n    Assert.assertNull(template.username);\n    Assert.assertNull(template.secret);\n  }\n\n  @Test\n  public void testRetrieve()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    List<String> command = Arrays.asList(Paths.get(\"/foo/bar\").toString(), \"get\");\n    Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder);\n\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\", Paths.get(\"/foo/bar\"), systemProperties, processBuilderFactory);\n    Credential credential = credentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credential.getUsername());\n    Assert.assertEquals(\"mysecret\", credential.getPassword());\n\n    Mockito.verify(processBuilderFactory).apply(command);\n  }\n\n  @Test\n  public void testRetrieveWithEnvironment()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    List<String> command = Arrays.asList(Paths.get(\"/foo/bar\").toString(), \"get\");\n    Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder);\n    Map<String, String> processBuilderEnvironment = Mockito.spy(new HashMap<>());\n    Mockito.when(processBuilder.environment()).thenReturn(processBuilderEnvironment);\n\n    Map<String, String> credHelperEnvironment = ImmutableMap.of(\"ENV_VARIABLE\", \"Value\");\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\",\n            Paths.get(\"/foo/bar\"),\n            systemProperties,\n            processBuilderFactory,\n            credHelperEnvironment);\n    Credential credential = credentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credential.getUsername());\n    Assert.assertEquals(\"mysecret\", credential.getPassword());\n\n    Mockito.verify(processBuilderFactory).apply(command);\n    Mockito.verify(processBuilderEnvironment).putAll(credHelperEnvironment);\n    Assert.assertEquals(1, processBuilderEnvironment.size());\n    Assert.assertEquals(\"Value\", processBuilderEnvironment.get(\"ENV_VARIABLE\"));\n  }\n\n  @Test\n  public void testRetrieve_cmdSuffixAddedOnWindows()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    systemProperties.setProperty(\"os.name\", \"WINdows\");\n    List<String> command = Arrays.asList(Paths.get(\"/foo/bar.cmd\").toString(), \"get\");\n    Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder);\n\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\", Paths.get(\"/foo/bar\"), systemProperties, processBuilderFactory);\n    Credential credential = credentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credential.getUsername());\n    Assert.assertEquals(\"mysecret\", credential.getPassword());\n\n    Mockito.verify(processBuilderFactory).apply(command);\n  }\n\n  @Test\n  public void testRetrieve_cmdSuffixAlreadyGivenOnWindows()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    systemProperties.setProperty(\"os.name\", \"WINdows\");\n    List<String> command = Arrays.asList(Paths.get(\"/foo/bar.CmD\").toString(), \"get\");\n    Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder);\n\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\", Paths.get(\"/foo/bar.CmD\"), systemProperties, processBuilderFactory);\n    Credential credential = credentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credential.getUsername());\n    Assert.assertEquals(\"mysecret\", credential.getPassword());\n\n    Mockito.verify(processBuilderFactory).apply(command);\n  }\n\n  @Test\n  public void testRetrieve_exeSuffixAlreadyGivenOnWindows()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    systemProperties.setProperty(\"os.name\", \"WINdows\");\n    List<String> command = Arrays.asList(Paths.get(\"/foo/bar.eXE\").toString(), \"get\");\n    Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder);\n\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\", Paths.get(\"/foo/bar.eXE\"), systemProperties, processBuilderFactory);\n    Credential credential = credentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credential.getUsername());\n    Assert.assertEquals(\"mysecret\", credential.getPassword());\n\n    Mockito.verify(processBuilderFactory).apply(command);\n  }\n\n  @Test\n  public void testRetrieve_cmdSuffixNotFoundOnWindows()\n      throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException,\n          IOException {\n    systemProperties.setProperty(\"os.name\", \"WINdows\");\n    List<String> errorCmdCommand = Arrays.asList(Paths.get(\"/foo/bar.cmd\").toString(), \"get\");\n    List<String> errorExeCommand = Arrays.asList(Paths.get(\"/foo/bar.exe\").toString(), \"get\");\n    List<String> command = Arrays.asList(Paths.get(\"/foo/bar\").toString(), \"get\");\n    Mockito.when(processBuilderFactory.apply(errorCmdCommand)).thenReturn(errorProcessBuilder);\n    Mockito.when(processBuilderFactory.apply(errorExeCommand)).thenReturn(errorProcessBuilder);\n    Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder);\n\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\", Paths.get(\"/foo/bar\"), systemProperties, processBuilderFactory);\n    Credential credential = credentialHelper.retrieve();\n    Assert.assertEquals(\"myusername\", credential.getUsername());\n    Assert.assertEquals(\"mysecret\", credential.getPassword());\n\n    Mockito.verify(processBuilderFactory).apply(errorCmdCommand);\n    Mockito.verify(processBuilderFactory).apply(errorExeCommand);\n    Mockito.verify(processBuilderFactory).apply(command);\n  }\n\n  @Test\n  public void testRetrieve_fileNotFoundExceptionMessage()\n      throws CredentialHelperUnhandledServerUrlException, IOException {\n    Mockito.when(processBuilderFactory.apply(Mockito.any())).thenReturn(processBuilder);\n    Mockito.when(processBuilder.start())\n        .thenThrow(\n            new IOException(\n                \"CreateProcess error=2, Das System kann die angegebene Datei nicht finden\"));\n\n    DockerCredentialHelper credentialHelper =\n        dockerCredentialHelper(\n            \"serverUrl\", Paths.get(\"/ignored\"), systemProperties, processBuilderFactory);\n    try {\n      credentialHelper.retrieve();\n      Assert.fail();\n    } catch (CredentialHelperNotFoundException ex) {\n      Assert.assertNotNull(ex.getMessage());\n    }\n  }\n\n  private DockerCredentialHelper dockerCredentialHelper(\n      String serverUrl,\n      Path credentialHelper,\n      Properties properties,\n      Function<List<String>, ProcessBuilder> processBuilderFactory) {\n    return dockerCredentialHelper(\n        serverUrl, credentialHelper, properties, processBuilderFactory, Collections.emptyMap());\n  }\n\n  private DockerCredentialHelper dockerCredentialHelper(\n      String serverUrl,\n      Path credentialHelper,\n      Properties properties,\n      Function<List<String>, ProcessBuilder> processBuilderFactory,\n      Map<String, String> environment) {\n    return new DockerCredentialHelper(\n        serverUrl, credentialHelper, properties, processBuilderFactory, environment);\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.tar;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport org.junit.Assert;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link TarExtractor}. */\npublic class TarExtractorTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testExtract() throws URISyntaxException, IOException {\n    Path source = Paths.get(Resources.getResource(\"core/extract.tar\").toURI());\n    Path destination = temporaryFolder.getRoot().toPath();\n    TarExtractor.extract(source, destination);\n\n    Assert.assertTrue(Files.exists(destination.resolve(\"file A\")));\n    Assert.assertTrue(Files.exists(destination.resolve(\"file B\")));\n    Assert.assertTrue(\n        Files.exists(destination.resolve(\"folder\").resolve(\"nested folder\").resolve(\"file C\")));\n\n    try (Stream<String> lines = Files.lines(destination.resolve(\"file A\"))) {\n      String contents = lines.collect(Collectors.joining());\n      Assert.assertEquals(\"Hello\", contents);\n    }\n  }\n\n  @Test\n  public void testExtract_missingDirectoryEntries() throws URISyntaxException, IOException {\n    Path source = Paths.get(Resources.getResource(\"core/extract-missing-dirs.tar\").toURI());\n    Path destination = temporaryFolder.getRoot().toPath();\n    TarExtractor.extract(source, destination);\n\n    Assert.assertTrue(Files.exists(destination.resolve(\"world\")));\n    Assert.assertTrue(\n        Files.exists(destination.resolve(\"a\").resolve(\"b\").resolve(\"c\").resolve(\"world\")));\n\n    try (Stream<String> lines = Files.lines(destination.resolve(\"world\"))) {\n      String contents = lines.collect(Collectors.joining());\n      Assert.assertEquals(\"world\", contents);\n    }\n  }\n\n  @Test\n  public void testExtract_symlinks() throws URISyntaxException, IOException {\n    Path source = Paths.get(Resources.getResource(\"core/symlinks.tar\").toURI());\n    Path destination = temporaryFolder.getRoot().toPath();\n    TarExtractor.extract(source, destination);\n\n    Assert.assertTrue(Files.isDirectory(destination.resolve(\"directory1\")));\n    Assert.assertTrue(Files.isDirectory(destination.resolve(\"directory2\")));\n    Assert.assertTrue(Files.isRegularFile(destination.resolve(\"directory2/regular\")));\n    Assert.assertTrue(Files.isSymbolicLink(destination.resolve(\"directory-symlink\")));\n    Assert.assertTrue(Files.isSymbolicLink(destination.resolve(\"directory1/file-symlink\")));\n  }\n\n  @Test\n  public void testExtract_modificationTimePreserved() throws URISyntaxException, IOException {\n    Path source = Paths.get(Resources.getResource(\"core/extract.tar\").toURI());\n    Path destination = temporaryFolder.getRoot().toPath();\n\n    TarExtractor.extract(source, destination);\n\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"file A\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(Instant.parse(\"2019-08-01T16:13:09Z\"));\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"file B\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(Instant.parse(\"2019-08-01T16:12:00Z\"));\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"folder\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(Instant.parse(\"2019-08-01T16:12:33Z\"));\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"folder/nested folder\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(Instant.parse(\"2019-08-01T16:13:30Z\"));\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"folder/nested folder/file C\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(Instant.parse(\"2019-08-01T16:12:21Z\"));\n  }\n\n  @Test\n  public void testExtract_reproducibleTimestampsEnabled() throws URISyntaxException, IOException {\n    // The tarfile has only level1/level2/level3/file.txt packaged\n    Path source = Paths.get(Resources.getResource(\"core/tarfile-only-file-packaged.tar\").toURI());\n\n    Path destination = temporaryFolder.getRoot().toPath();\n\n    TarExtractor.extract(source, destination, true);\n\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"level-1\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(FileTime.fromMillis(1000L).toInstant());\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"level-1/level-2\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(FileTime.fromMillis(1000L).toInstant());\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"level-1/level-2/level-3\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(FileTime.fromMillis(1000L).toInstant());\n    assertThat(\n            Files.getLastModifiedTime(destination.resolve(\"level-1/level-2/level-3/file.txt\"))\n                .toInstant()\n                .truncatedTo(ChronoUnit.SECONDS))\n        .isEqualTo(Instant.parse(\"2021-01-29T21:10:02Z\"));\n  }\n\n  @Test\n  public void testExtract_reproducibleTimestampsEnabled_destinationNotEmpty() throws IOException {\n    Path destination = temporaryFolder.getRoot().toPath();\n    temporaryFolder.newFile();\n\n    IllegalStateException exception =\n        assertThrows(\n            IllegalStateException.class,\n            () -> TarExtractor.extract(Paths.get(\"ignore\"), destination, true));\n    assertThat(exception).hasMessageThat().startsWith(\"Cannot enable reproducible timestamps\");\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarStreamBuilderTest.java",
    "content": "/*\n * Copyright 2017 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.tar;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/** Tests for {@link TarStreamBuilder}. */\npublic class TarStreamBuilderTest {\n\n  private Path fileA;\n  private Path fileB;\n  private Path directoryA;\n  private byte[] fileAContents;\n  private byte[] fileBContents;\n  private final TarStreamBuilder testTarStreamBuilder = new TarStreamBuilder();\n\n  @Before\n  public void setup() throws URISyntaxException, IOException {\n    // Gets the test resource files.\n    fileA = Paths.get(Resources.getResource(\"core/fileA\").toURI());\n    fileB = Paths.get(Resources.getResource(\"core/fileB\").toURI());\n    directoryA = Paths.get(Resources.getResource(\"core/directoryA\").toURI());\n\n    fileAContents = Files.readAllBytes(fileA);\n    fileBContents = Files.readAllBytes(fileB);\n  }\n\n  @Test\n  public void testToBlob_tarArchiveEntries() throws IOException {\n    setUpWithTarEntries();\n    verifyBlobWithoutCompression();\n  }\n\n  @Test\n  public void testToBlob_strings() throws IOException {\n    setUpWithStrings();\n    verifyBlobWithoutCompression();\n  }\n\n  @Test\n  public void testToBlob_stringsAndTarArchiveEntries() throws IOException {\n    setUpWithStringsAndTarEntries();\n    verifyBlobWithoutCompression();\n  }\n\n  @Test\n  public void testToBlob_tarArchiveEntriesWithCompression() throws IOException {\n    setUpWithTarEntries();\n    verifyBlobWithCompression();\n  }\n\n  @Test\n  public void testToBlob_stringsWithCompression() throws IOException {\n    setUpWithStrings();\n    verifyBlobWithCompression();\n  }\n\n  @Test\n  public void testToBlob_stringsAndTarArchiveEntriesWithCompression() throws IOException {\n    setUpWithStringsAndTarEntries();\n    verifyBlobWithCompression();\n  }\n\n  @Test\n  public void testToBlob_multiByte() throws IOException {\n    testTarStreamBuilder.addByteEntry(\n        \"日本語\".getBytes(StandardCharsets.UTF_8), \"test\", Instant.EPOCH);\n    testTarStreamBuilder.addByteEntry(\n        \"asdf\".getBytes(StandardCharsets.UTF_8), \"crepecake\", Instant.EPOCH);\n    testTarStreamBuilder.addBlobEntry(\n        Blobs.from(\"jib\"), \"jib\".getBytes(StandardCharsets.UTF_8).length, \"jib\", Instant.EPOCH);\n\n    // Writes the BLOB and captures the output.\n    ByteArrayOutputStream tarByteOutputStream = new ByteArrayOutputStream();\n    OutputStream compressorStream = new GZIPOutputStream(tarByteOutputStream);\n    testTarStreamBuilder.writeAsTarArchiveTo(compressorStream);\n\n    // Rearrange the output into input for verification.\n    ByteArrayInputStream byteArrayInputStream =\n        new ByteArrayInputStream(tarByteOutputStream.toByteArray());\n    InputStream tarByteInputStream = new GZIPInputStream(byteArrayInputStream);\n    TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(tarByteInputStream);\n\n    // Verify multi-byte characters are written/read correctly\n    TarArchiveEntry headerFile = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\"test\", headerFile.getName());\n    Assert.assertEquals(\n        \"日本語\", new String(ByteStreams.toByteArray(tarArchiveInputStream), StandardCharsets.UTF_8));\n\n    headerFile = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\"crepecake\", headerFile.getName());\n    Assert.assertEquals(\n        \"asdf\", new String(ByteStreams.toByteArray(tarArchiveInputStream), StandardCharsets.UTF_8));\n\n    headerFile = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\"jib\", headerFile.getName());\n    Assert.assertEquals(\n        \"jib\", new String(ByteStreams.toByteArray(tarArchiveInputStream), StandardCharsets.UTF_8));\n\n    Assert.assertNull(tarArchiveInputStream.getNextEntry());\n  }\n\n  @Test\n  public void testToBlob_modificationTime() throws IOException {\n    testTarStreamBuilder.addByteEntry(\n        \"foo\".getBytes(StandardCharsets.UTF_8), \"foo\", Instant.ofEpochSecond(1234));\n    testTarStreamBuilder.addBlobEntry(\n        Blobs.from(\"bar\"),\n        \"bar\".getBytes(StandardCharsets.UTF_8).length,\n        \"bar\",\n        Instant.ofEpochSecond(3));\n\n    ByteArrayOutputStream outStream = new ByteArrayOutputStream();\n    testTarStreamBuilder.writeAsTarArchiveTo(outStream);\n\n    TarArchiveInputStream tarInStream =\n        new TarArchiveInputStream(new ByteArrayInputStream(outStream.toByteArray()));\n    TarArchiveEntry entry1 = tarInStream.getNextEntry();\n    TarArchiveEntry entry2 = tarInStream.getNextEntry();\n\n    assertThat(entry1.getName()).isEqualTo(\"foo\");\n    assertThat(entry1.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(1234));\n    assertThat(entry2.getName()).isEqualTo(\"bar\");\n    assertThat(entry2.getModTime().toInstant()).isEqualTo(Instant.ofEpochSecond(3));\n  }\n\n  /** Creates a TarStreamBuilder using TarArchiveEntries. */\n  private void setUpWithTarEntries() throws IOException {\n    // Prepares a test TarStreamBuilder.\n    testTarStreamBuilder.addTarArchiveEntry(\n        new TarArchiveEntry(fileA, \"some/path/to/resourceFileA\"));\n    testTarStreamBuilder.addTarArchiveEntry(new TarArchiveEntry(fileB, \"crepecake\"));\n    testTarStreamBuilder.addTarArchiveEntry(new TarArchiveEntry(directoryA, \"some/path/to\"));\n    testTarStreamBuilder.addTarArchiveEntry(\n        new TarArchiveEntry(\n            fileA,\n            \"some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890\"));\n  }\n\n  /** Creates a TarStreamBuilder using Strings. */\n  private void setUpWithStrings() throws IOException {\n    // Prepares a test TarStreamBuilder.\n    testTarStreamBuilder.addByteEntry(fileAContents, \"some/path/to/resourceFileA\", Instant.EPOCH);\n    testTarStreamBuilder.addByteEntry(fileBContents, \"crepecake\", Instant.EPOCH);\n    testTarStreamBuilder.addTarArchiveEntry(new TarArchiveEntry(directoryA, \"some/path/to\"));\n    testTarStreamBuilder.addByteEntry(\n        fileAContents,\n        \"some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890\",\n        Instant.EPOCH);\n  }\n\n  /** Creates a TarStreamBuilder using Strings and TarArchiveEntries. */\n  private void setUpWithStringsAndTarEntries() throws IOException {\n    // Prepares a test TarStreamBuilder.\n    testTarStreamBuilder.addByteEntry(fileAContents, \"some/path/to/resourceFileA\", Instant.EPOCH);\n    testTarStreamBuilder.addTarArchiveEntry(new TarArchiveEntry(fileB, \"crepecake\"));\n    testTarStreamBuilder.addTarArchiveEntry(new TarArchiveEntry(directoryA, \"some/path/to\"));\n    testTarStreamBuilder.addByteEntry(\n        fileAContents,\n        \"some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890\",\n        Instant.EPOCH);\n  }\n\n  /** Creates a compressed blob from the TarStreamBuilder and verifies it. */\n  private void verifyBlobWithCompression() throws IOException {\n    // Writes the BLOB and captures the output.\n    ByteArrayOutputStream tarByteOutputStream = new ByteArrayOutputStream();\n    OutputStream compressorStream = new GZIPOutputStream(tarByteOutputStream);\n    testTarStreamBuilder.writeAsTarArchiveTo(compressorStream);\n\n    // Rearrange the output into input for verification.\n    ByteArrayInputStream byteArrayInputStream =\n        new ByteArrayInputStream(tarByteOutputStream.toByteArray());\n    InputStream tarByteInputStream = new GZIPInputStream(byteArrayInputStream);\n    TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(tarByteInputStream);\n    verifyTarArchive(tarArchiveInputStream);\n  }\n\n  /** Creates an uncompressed blob from the TarStreamBuilder and verifies it. */\n  private void verifyBlobWithoutCompression() throws IOException {\n    // Writes the BLOB and captures the output.\n    ByteArrayOutputStream tarByteOutputStream = new ByteArrayOutputStream();\n    testTarStreamBuilder.writeAsTarArchiveTo(tarByteOutputStream);\n\n    // Rearrange the output into input for verification.\n    ByteArrayInputStream byteArrayInputStream =\n        new ByteArrayInputStream(tarByteOutputStream.toByteArray());\n    TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(byteArrayInputStream);\n    verifyTarArchive(tarArchiveInputStream);\n  }\n\n  /**\n   * Helper method to verify that the files were archived correctly by reading {@code\n   * tarArchiveInputStream}.\n   */\n  private void verifyTarArchive(TarArchiveInputStream tarArchiveInputStream) throws IOException {\n    // Verifies fileA was archived correctly.\n    TarArchiveEntry headerFileA = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\"some/path/to/resourceFileA\", headerFileA.getName());\n    byte[] fileAString = ByteStreams.toByteArray(tarArchiveInputStream);\n    Assert.assertArrayEquals(fileAContents, fileAString);\n\n    // Verifies fileB was archived correctly.\n    TarArchiveEntry headerFileB = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\"crepecake\", headerFileB.getName());\n    byte[] fileBString = ByteStreams.toByteArray(tarArchiveInputStream);\n    Assert.assertArrayEquals(fileBContents, fileBString);\n\n    // Verifies directoryA was archived correctly.\n    TarArchiveEntry headerDirectoryA = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\"some/path/to/\", headerDirectoryA.getName());\n\n    // Verifies the long file was archived correctly.\n    TarArchiveEntry headerFileALong = tarArchiveInputStream.getNextEntry();\n    Assert.assertEquals(\n        \"some/really/long/path/that/exceeds/100/characters/abcdefghijklmnopqrstuvwxyz0123456789012345678901234567890\",\n        headerFileALong.getName());\n    byte[] fileALongString = ByteStreams.toByteArray(tarArchiveInputStream);\n    Assert.assertArrayEquals(fileAContents, fileALongString);\n\n    Assert.assertNull(tarArchiveInputStream.getNextEntry());\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/resources/META-INF/services/com.google.cloud.tools.jib.api.DockerClient",
    "content": "com.google.cloud.tools.jib.docker.AnotherDockerClient"
  },
  {
    "path": "jib-core/src/test/resources/core/application/dependencies/libraryA.jar",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/application/dependencies/libraryB.jar",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/application/dependencies/more/dependency-1.0.0.jar",
    "content": "]e$\u000fỀx\u0017,\u001b\n.3I݅3\u001c8V\u0014KA\u0003M\u0007)=5~'qю$[-\t:&% \u001cEo\u00067Ns`iZ\u00040\u0019MT.9J[}?\u000f\\E\f\u0019}UvJd\u000eo(i\u0019\"Mԛ_+/\u001dcI<Zje44%\u001fd2\u000e?l>.-\bO=\u0013Hi"
  },
  {
    "path": "jib-core/src/test/resources/core/application/resources/resourceA",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/application/resources/resourceB",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/application/resources/world",
    "content": "world"
  },
  {
    "path": "jib-core/src/test/resources/core/blobA",
    "content": "aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz"
  },
  {
    "path": "jib-core/src/test/resources/core/class-finder-tests/simple/NotEvenAClass.txt",
    "content": "Hi there."
  },
  {
    "path": "jib-core/src/test/resources/core/directoryA/.gitkeep",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/docker/emptyFile",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/extraction/test-cache/local/config/066872f17ae819f846a6d5abcfc3165abe13fb0a157640fa8cb7af81077670c0",
    "content": "{\"created\":\"1970-01-01T00:00:01Z\",\"architecture\":\"amd64\",\"os\":\"linux\",\"config\":{\"Env\":[],\"Entrypoint\":[\"java\",\"-cp\",\"/app/resources:/app/classes:/app/libs/*\",\"Hello\"],\"ExposedPorts\":{},\"Labels\":{\"label1\":\"value1\",\"label2\":\"value2\"},\"Volumes\":{}},\"history\":[{\"created\":\"1970-01-01T00:00:01Z\",\"author\":\"Jib\",\"created_by\":\"jib-gradle-plugin:1.4.1-SNAPSHOT\",\"comment\":\"classes\"},{\"created\":\"1970-01-01T00:00:01Z\",\"author\":\"Jib\",\"created_by\":\"jib-gradle-plugin:1.4.1-SNAPSHOT\",\"comment\":\"extra files\"}],\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd\",\"sha256:f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e\"]}}"
  },
  {
    "path": "jib-core/src/test/resources/core/fileA",
    "content": "Crepe cakes are good."
  },
  {
    "path": "jib-core/src/test/resources/core/fileB",
    "content": "Fast image builds are great."
  },
  {
    "path": "jib-core/src/test/resources/core/json/basic.json",
    "content": "{\"number\":54,\"text\":\"crepecake\",\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"innerObject\":{\"number\":23,\"texts\":[\"first text\",\"second text\"],\"digests\":[\"sha256:91e0cae00b86c289b33fee303a807ae72dd9f0315c16b74e6ab0cdbe9d996c10\",\"sha256:4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\"]},\"list\":[{\"number\":42,\"texts\":[]},{\"number\":99,\"texts\":[\"some text\"],\"digests\":[\"sha256:d38f571aa1c11e3d516e0ef7e513e7308ccbeb869770cb8c4319d63b10a0075e\"]}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/basic_list.json",
    "content": "[{\"number\":1,\"text\":\"text1\",\"digest\":\"sha256:91e0cae00b86c289b33fee303a807ae72dd9f0315c16b74e6ab0cdbe9d996c10\",\"innerObject\":{\"number\":10},\"list\":[{\"number\":11},{\"number\":12}]},{\"number\":2,\"text\":\"text2\",\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"innerObject\":{\"number\":20},\"list\":[]}]"
  },
  {
    "path": "jib-core/src/test/resources/core/json/containerconfig.json",
    "content": "{\"created\":\"1970-01-01T00:00:20Z\",\"architecture\":\"wasm\",\"os\":\"js\",\"config\":{\"Env\":[\"VAR1=VAL1\",\"VAR2=VAL2\"],\"Entrypoint\":[\"some\",\"entrypoint\",\"command\"],\"Cmd\":[\"arg1\",\"arg2\"],\"Healthcheck\":{\"Test\":[\"CMD-SHELL\",\"/checkhealth\"],\"Interval\":3000000000,\"Timeout\":1000000000,\"StartPeriod\":2000000000,\"Retries\":3},\"ExposedPorts\":{\"1000/tcp\":{},\"2000/tcp\":{},\"3000/udp\":{}},\"Labels\":{\"key1\":\"value1\",\"key2\":\"value2\"},\"WorkingDir\":\"/some/workspace\",\"User\":\"tomcat\",\"Volumes\":{\"/var/job-result-data\":{},\"/var/log/my-app-logs\":{}}},\"history\":[{\"created\":\"1970-01-01T00:00:00Z\",\"author\":\"Bazel\",\"created_by\":\"bazel build ...\",\"empty_layer\":true},{\"created\":\"1970-01-01T00:00:20Z\",\"author\":\"Jib\",\"created_by\":\"jib\"}],\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"]}}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/dockerconfig.json",
    "content": "{\n  \"auths\":{\n    \"some other registry\":{\"auth\":\"c29tZTpvdGhlcjphdXRo\"},\n    \"some registry\":{\"auth\":\"c29tZTphdXRo\",\"password\":\"ignored\"},\n    \"auth and userpw registry\":{\"auth\":\"c29tZTphdXRo\",\"username\":\"ignored\",\"password\":\"ignored\"},\n    \"userpw registry\":{\"username\":\"someuser\",\"password\":\"somepw\"},\n    \"https://registry\":{\"auth\":\"dG9rZW4=\"},\n\n    \"example.com\":{\"auth\":\"should not match example\"},\n    \"no auth field\":{}\n  },\n  \"credsStore\":\"some credential store\",\n  \"credHelpers\":{\n    \"another registry\":\"another credential helper\",\n    \"some registry\":\"some credential helper\",\n    \"index.docker.io\":\"index.docker.io credential helper\",\n\n    \"just.registry.in.helpers\":\"credHelper for just.registry.in.helpers\",\n    \"https://with.protocol.in.helpers\":\"credHelper for https__with.protocol.in.helpers\",\n    \"with.suffix.in.helpers/v2/\":\"credHelper for with.suffix.in.helpers/v2/\",\n    \"https://with.protocol.and.suffix.in.helpers/suffix\":\n        \"credHelper for https__with.protocol.and.suffix.in.helpers/suffix\",\n\n    \"another.example.com.in.helpers\":\"should not match example\"\n  }\n}\n"
  },
  {
    "path": "jib-core/src/test/resources/core/json/dockerconfig_extra_matches.json",
    "content": "{\n  \"auths\":{\n    \"https://my-registry/v1/\":{\"auth\":\"my-registry: starting with name and with https\"},\n    \"my-registry/v4/\":{\"auth\":\"my-registry: starting with name\"},\n    \"https://my-registry\":{\"auth\":\"my-registry: with https\"},\n    \"my-registry\":{\"auth\":\"my-registry: exact match\"},\n\n    \"https://cool-registry\":{\"auth\":\"cool-registry: with https\"},\n    \"cool-registry/v8/\":{\"auth\":\"cool-registry: starting with registry\"},\n    \"https://cool-registry/v1/\":{\"auth\":\"cool-registry: starting with name and with https\"},\n\n    \"https://awesome-registry/v9/\":{\"auth\":\"awesome-registry: starting with name and with https\"},\n    \"awesome-registry/v9/\":{\"auth\":\"awesome-registry: starting with name\"},\n\n    \"https://dull-registry/v3/\":{\"auth\":\"dull-registry: starting with name and with https\"}\n  }\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/dockerconfig_identity_token.json",
    "content": "{\n  \"auths\":{\n    \"some registry\":{\n      \"auth\":\"MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwOg==\",\n      \"identitytoken\":\"cool identity token\"\n    }\n  }\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/dockerconfig_index_docker_io_v1.json",
    "content": "{\n  \"auths\":{\n    \"index.docker.io/v1/\":{\"auth\":\"dG9rZW4gZm9yOiBpbmRleC5kb2NrZXIuaW8vdjEv\"}\n  }\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/legacy_dockercfg",
    "content": "{\n    \"some registry\":{\"auth\":\"c29tZTpvdGhlcjphdXRo\"},\n    \"https://example.com/v2/\":{\"auth\":\"dXNlcjpwYXNz\"}\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/loadmanifest.json",
    "content": "[{\"Config\":\"config.json\",\"RepoTags\":[\"testregistry/testrepo:testtag\"],\"Layers\":[\"layer1.tar.gz\",\"layer2.tar.gz\",\"layer3.tar.gz\"]}]"
  },
  {
    "path": "jib-core/src/test/resources/core/json/loadmanifest2.json",
    "content": "[{\"Config\":\"config.json\",\"RepoTags\":[\"testregistry/testrepo:testtag\"],\"Layers\":[\"layer1.tar.gz\",\"layer2.tar.gz\",\"layer3.tar.gz\"],\"LayerSources\":{}}]\n"
  },
  {
    "path": "jib-core/src/test/resources/core/json/metadata-v2.json",
    "content": "{\"layers\":[{\"reference\":{\"size\":631,\"digest\":\"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\",\"diffId\":\"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"}},{\"reference\":{\"size\":223,\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"diffId\":\"sha256:a3f3e99c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a8372\"},\"properties\":{\"layerEntries\":[{\"sourceFiles\":[\"some/source/path\"],\"extractionPath\":\"some/extraction/path\"}],\"lastModifiedTime\":255073580723571}}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/metadata-v3.json",
    "content": "{\"layers\":[{\"reference\":{\"size\":631,\"digest\":\"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\",\"diffId\":\"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"}},{\"reference\":{\"size\":223,\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"diffId\":\"sha256:a3f3e99c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a8372\"},\"properties\":{\"layerEntries\":[{\"sourceFile\":\"/some/source/path\",\"extractionPath\":\"/some/extraction/path\"}],\"lastModifiedTime\":255073580723571}}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/metadata.json",
    "content": "{\"layers\":[{\"reference\":{\"size\":631,\"digest\":\"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\",\"diffId\":\"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"}},{\"reference\":{\"size\":223,\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"diffId\":\"sha256:a3f3e99c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a8372\"},\"properties\":{\"sourceFiles\":[\"some/source/path\"],\"lastModifiedTime\":255073580723571}}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/metadata_corrupted.json",
    "content": "{\"layers\":[{\"reference\":{\"size\":223,\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"diffId\":\"sha256:a3f3e99c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a8372\"}}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/metadata_windows-v2.json",
    "content": "{\"layers\":[{\"reference\":{\"size\":631,\"digest\":\"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\",\"diffId\":\"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"}},{\"reference\":{\"size\":223,\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"diffId\":\"sha256:a3f3e99c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a8372\"},\"properties\":{\"layerEntries\":[{\"sourceFiles\":[\"some\\\\source\\\\path\"],\"extractionPath\":\"some/extraction/path\"}],\"lastModifiedTime\":255073580723571}}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/metadata_windows.json",
    "content": "{\"layers\":[{\"reference\":{\"size\":631,\"digest\":\"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\",\"diffId\":\"sha256:b56ae66c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a4647\"}},{\"reference\":{\"size\":223,\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"diffId\":\"sha256:a3f3e99c29370df48e7377c8f9baa744a3958058a766793f821dadcb144a8372\"},\"properties\":{\"sourceFiles\":[\"some\\\\source\\\\path\"],\"lastModifiedTime\":255073580723571}}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/ociindex.json",
    "content": "{\n  \"schemaVersion\": 2,\n  \"mediaType\": \"application/vnd.oci.image.index.v1+json\",\n  \"manifests\": [\n    {\n      \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n      \"digest\": \"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\n      \"size\": 1000,\n      \"annotations\": {\n        \"org.opencontainers.image.ref.name\": \"regis.try/repo:tag\"\n      }\n    }\n  ]\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/ociindex_platforms.json",
    "content": "{\n  \"schemaVersion\": 2,\n  \"mediaType\": \"application/vnd.oci.image.index.v1+json\",\n  \"manifests\": [\n    {\n      \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n      \"digest\": \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n      \"size\": 7143,\n      \"platform\": {\n        \"architecture\": \"ppc64le\",\n        \"os\": \"linux\"\n      }\n    },\n    {\n      \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n      \"digest\": \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n      \"size\": 7682,\n      \"platform\": {\n        \"architecture\": \"amd64\",\n        \"os\": \"linux\"\n      }\n    }\n  ]\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/ocimanifest.json",
    "content": "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"config\":{\"mediaType\":\"application/vnd.oci.image.config.v1+json\",\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"size\":1000},\"layers\":[{\"mediaType\":\"application/vnd.oci.image.layer.v1.tar+gzip\",\"digest\":\"sha256:4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\",\"size\":1000000}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/translated_ocimanifest.json",
    "content": "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"config\":{\"mediaType\":\"application/vnd.oci.image.config.v1+json\",\"digest\":\"sha256:9df7a2b76e1b35dcf443c4b6a08e40a5d040120022293f7d03ae082f95835839\",\"size\":686},\"layers\":[{\"mediaType\":\"application/vnd.oci.image.layer.v1.tar+gzip\",\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"size\":1000}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/translated_v22manifest.json",
    "content": "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\"config\":{\"mediaType\":\"application/vnd.docker.container.image.v1+json\",\"digest\":\"sha256:2000a70a1ce8bba401c493376fdb9eb81bcf7155212f4ce4c6ff96e577718a49\",\"size\":818},\"layers\":[{\"mediaType\":\"application/vnd.docker.image.rootfs.diff.tar.gzip\",\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"size\":1000}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/v21manifest.json",
    "content": "{\n  \"schemaVersion\":1,\n  \"fsLayers\": [\n    {\"blobSum\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\"},\n    {\"blobSum\":\"sha256:5bd451067f9ab05e97cda8476c82f86d9b69c2dffb60a8ad2fe3723942544ab3\"}\n  ],\n  \"history\": [\n    {\"v1Compatibility\": \"{\\\"architecture\\\":\\\"ppc64le\\\",\\\"config\\\":{\\\"Env\\\":[\\\"JAVA_HOME=/opt/openjdk\\\",\\\"PATH=/opt/openjdk/bin\\\"],\\\"Entrypoint\\\":[\\\"/opt/openjdk/bin/java\\\"],\\\"Cmd\\\":[\\\"-version\\\"]},\\\"created\\\":\\\"2019-04-17T14:02:34.79404123Z\\\"}\"},\n    {\"v1Compatibility\":\"another v1-compatible object\"}\n  ]\n}\n"
  },
  {
    "path": "jib-core/src/test/resources/core/json/v22manifest.json",
    "content": "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\"config\":{\"mediaType\":\"application/vnd.docker.container.image.v1+json\",\"digest\":\"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad\",\"size\":1000},\"layers\":[{\"mediaType\":\"application/vnd.docker.image.rootfs.diff.tar.gzip\",\"digest\":\"sha256:4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236\",\"size\":1000000}]}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/v22manifest_list.json",
    "content": "{\n  \"schemaVersion\": 2,\n  \"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\",\n  \"manifests\": [\n    {\n      \"mediaType\": \"application/vnd.docker.image.manifest.v2+json\",\n      \"size\": 7143,\n      \"digest\": \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n      \"platform\": {\n        \"architecture\": \"ppc64le\",\n        \"os\": \"linux\"\n      }\n    },\n    {\n      \"mediaType\": \"application/vnd.docker.image.manifest.v2+json\",\n      \"size\": 7682,\n      \"digest\": \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n      \"platform\": {\n        \"architecture\": \"amd64\",\n        \"os\": \"linux\",\n        \"features\": [\n          \"sse4\"\n        ]\n      }\n    },\n    {\n      \"mediaType\": \"application/vnd.docker.image.manifest.v2+json\",\n      \"size\": 7682,\n      \"digest\": \"sha256:cccbcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501999\",\n      \"platform\": {\n        \"architecture\": \"amd64\",\n        \"os\": \"linux\",\n        \"os.version\": \"potato-9\"\n      }\n    }\n  ]\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/json/v22manifest_optional_properties.json",
    "content": "{\n  \"schemaVersion\": 2,\n  \"mediaType\": \"type\",\n  \"config\":{\n    \"mediaType\": \"type\",\n    \"digest\": \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n    \"size\": 10\n  },\n  \"layers\":[\n    {\n      \"mediaType\": \"type\",\n      \"digest\": \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"size\": 1\n    },\n    {\n      \"mediaType\": \"type\",\n      \"digest\": \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"size\": 2,\n      \"urls\": [\"url-foo\", \"url-bar\"]\n    },\n    {\n      \"mediaType\": \"type\",\n      \"digest\": \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"size\": 3,\n      \"annotations\": {\"key-foo\":\"value-foo\"}\n    },\n    {\n      \"mediaType\": \"type\",\n      \"digest\": \"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"size\": 4,\n      \"urls\": [\"cool-url\"],\n      \"annotations\": {\"key1\":\"value1\", \"key2\":\"value2\"}\n    }\n  ]\n}"
  },
  {
    "path": "jib-core/src/test/resources/core/layer/a/b/bar",
    "content": "bar\n"
  },
  {
    "path": "jib-core/src/test/resources/core/layer/c/cat",
    "content": "cat\n"
  },
  {
    "path": "jib-core/src/test/resources/core/layer/foo",
    "content": "foo\n"
  },
  {
    "path": "jib-core/src/test/resources/core/random-contents/file1",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/random-contents/file2",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/random-contents/sub-directory/file3",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/random-contents/sub-directory/file4",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/random-contents/sub-directory/leaf/file5",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/random-contents/sub-directory/leaf/file6",
    "content": ""
  },
  {
    "path": "jib-core/src/test/resources/core/webAppSampleDockerfile",
    "content": "FROM tomcat:8.5-jre8-alpine\n\nCOPY libs /\nCOPY snapshot-libs /\nCOPY resources /\nCOPY classes /\nCOPY root /\n\nENTRYPOINT [\"catalina.sh\",\"run\"]"
  },
  {
    "path": "jib-core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline\n"
  },
  {
    "path": "jib-gradle-plugin/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added \n\n### Changed\n\n### Fixed\n\n## 3.5.3\n\n### Fixed\n- fix: _jibSkaffoldFilesV2 task fails on Gradle 9.0 ([#4469](https://github.com/GoogleContainerTools/jib/issues/4469))\n\n## 3.5.2\n\n### Added\n- feat: Avoid use of StartParameter.getSettingsFile() in jibSkaffoldFilesV2 when Gradle version is 9 or higher\n\n## 3.5.1\n\n### Added\n- feat: support Java 25 main methods\n\n### Changed\n- deps: update `org.ow2.asm:asm` to version 9.9\n\n## 3.5.0\n\n### Added\n- feat: add default base image for Java 25 (#4436)\n\n### Changed\n- deps: update `org.ow2.asm:asm` to version 9.8 for java 25 support\n\n## 3.4.5\n\n### Fixed\n- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4267)\n\n\n## 3.4.4\n- fix: allow pushing images with different arch/os to docker daemon [#4265](https://github.com/GoogleContainerTools/jib/issues/4265)\n- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4267)\n\n## 3.4.3\n\n### Fixed\n- fix: When building to the local docker daemon with multiple platforms configured, Jib will now automatically select the image that matches the OS type and architecture of the local Docker environment. ([#4249](https://github.com/GoogleContainerTools/jib/pull/4249))\n\n## 3.4.2\n\n### Changed\n- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204))\n\n### Fixed\n- fix: set PAX headers to address build reproducibility issue ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204))\n- fix: (WAR Containerization) modify default entrypoint to `java -jar /usr/local/jetty/start.jar --module=ee10-deploy` for Jetty 12+ compatibility ([#4216](https://github.com/GoogleContainerTools/jib/pull/4216))\n\n## 3.4.1\n\n### Fixed\n- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171))\n\n## 3.4.0\n\n### Added\n- feat: support gradle lazy configuration for entrypoint container parameter. ([#4003](https://github.com/GoogleContainerTools/jib/pull/4003))\n\n### Changed\n- deps: bump com.github.luben:zstd-jni from 1.5.5-2 to 1.5.5-4. ([#4049](https://github.com/GoogleContainerTools/jib/pull/4049/))\n- deps: bump com.fasterxml.jackson:jackson-bom from 2.15.0 to 2.15.2. ([#4055](https://github.com/GoogleContainerTools/jib/pull/4055))\n- deps: bump com.google.guava:guava from 32.0.1-jre to 32.1.2-jre ([#4078](https://github.com/GoogleContainerTools/jib/pull/4078))\n- deps: bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9. ([#4098](https://github.com/GoogleContainerTools/jib/pull/4098))\n\n### Fixed\n- fix: fix WWW-Authenticate header parsing for Basic authentication ([#4035](https://github.com/GoogleContainerTools/jib/pull/4035/))\n- Fixed Gradle deprecations for Gradle 8.2. ([#3892](https://github.com/GoogleContainerTools/jib/pull/3892))\n\n## 3.3.2\n\n### Added\n- Support lazy configuration for `jib.container.mainClass` and `jib.container.jvmFlags` parameters ([#3936](https://github.com/GoogleContainerTools/jib/pull/3936))\n\n### Changed\n- Log an info instead of warning when entrypoint makes the image to ignore jvm parameters ([#3904](https://github.com/GoogleContainerTools/jib/pull/3904))\n\nThanks to our community contributors @rmannibucau, @erdi!\n\n## 3.3.1\n\n### Added\n- Added lazy evaluation for `jib.container.creationTime` and `jib.container.filesModificationTime` parameters using Gradle Property and Provider. ([#3709](https://github.com/GoogleContainerTools/jib/pull/3709))\n\n### Changed\n- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745))\n\n### Fixed\n- Fixed issue with `jibBuildTar`'s `UP-TO-DATE` check by adding back main `SourceSet`'s outputs to task dependency ([#3793](https://github.com/GoogleContainerTools/jib/pull/3793))\n\nThanks to our community contributors @creckord!\n\n## 3.3.0\n\n### Added\n\n- Included `imagePushed` field to image metadata json output file which provides information on whether an image was pushed by Jib. Note that the output file is `build/jib-image.json` by default or configurable with `jib.outputPaths.imageJson`. ([#3641](https://github.com/GoogleContainerTools/jib/pull/3641))\n- Added lazy evaluation for `jib.extraDirectories` parameters using Gradle Property and Provider. ([#3737](https://github.com/GoogleContainerTools/jib/issues/3737))\n- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)).\n- Support for OCI image index manifests ([#3715](https://github.com/GoogleContainerTools/jib/pull/3715)).\n- Support for base image layer compressed with zstd ([#3717](https://github.com/GoogleContainerTools/jib/pull/3717)).\n\n### Changed\n\n- Upgraded slf4j-api to 2.0.0 ([#3735](https://github.com/GoogleContainerTools/jib/pull/3735)).\n- Upgraded nullaway to 0.9.9 ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720))\n- Jib now only checks for file existence instead of running the executable passed into `dockerClient.executable` for the purpose of verifying if docker is installed correctly. Users are responsible for ensuring that the docker executable specified through this property is valid and has the correct permissions ([#3744](https://github.com/GoogleContainerTools/jib/pull/3744)).\n- Jib now throws an exception when the base image doesn't support target platforms during multi-platform build ([#3707](https://github.com/GoogleContainerTools/jib/pull/3707)).\n\nThanks to our community contributors @wwadge, @oliver-brm, @rquinio and @gsquared94!\n\n## 3.2.1\n\n### Added\n\n- Environment variables can now be used in configuring credential helpers. ([#2814](https://github.com/GoogleContainerTools/jib/issues/2814))\n  ```gradle\n   jib.to {\n      image = 'myimage'\n      credHelper {\n          helper = 'ecr-login'\n          environment = [\n              AWS_PROFILE: 'profile'\n          ]\n      }\n   }\n  ```\n  \n### Changed\n- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/pull/3612)).\n\n### Fixed\n\n- Fixed setting image format in Kotlin ([#3593](https://github.com/GoogleContainerTools/jib/pull/3593)).\n\n## 3.2.0\n\n### Added\n\n- [`jib.from.platforms`](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#from-closure) parameter for multi-architecture image building can now be configured through Maven and system properties (for example, `-Djib.from.platforms=linux/amd64,linux/arm64` on the command-line). ([#2742](https://github.com/GoogleContainerTools/jib/pull/2742))\n- For retrieving credentials, Jib additionally looks for `$XDG_RUNTIME_DIR/containers/auth.json`, `$XDG_CONFIG_HOME/containers/auth.json`, and `$HOME/.config/containers/auth.json`. ([#3524](https://github.com/GoogleContainerTools/jib/issues/3524))\n\n\n### Changed\n\n- Changed the default base image of the Jib CLI `jar` command from the `adoptopenjdk` images to the [`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin) on Docker Hub. Note that Temurin (by Adoptium) is the new name of AdoptOpenJDK. ([#3483](https://github.com/GoogleContainerTools/jib/issues/3483))\n- Build will fail if `extraDirectories.paths` contain `from` directory that doesn't exist locally ([#3542](https://github.com/GoogleContainerTools/jib/issues/3542))\n\n### Fixed\n\n- Fixed `ClassCastException` when using non-`String` value (for example, [`Provider`](https://docs.gradle.org/current/javadoc/org/gradle/api/provider/Provider.html)) for `Main-Class` manifest attribute of the `jar` task. ([#3396](https://github.com/GoogleContainerTools/jib/issues/3396))\n- Fixed incorrect parsing with comma escaping when providing Jib list or map property values on the command-line. ([#2224](https://github.com/GoogleContainerTools/jib/issues/2224))\n\n## 3.1.4\n\n### Changed\n\n- Downgraded Google HTTP libraries to 1.34.0 to resolve network issues. ([#3415](https://github.com/GoogleContainerTools/jib/pull/3415), [#3058](https://github.com/GoogleContainerTools/jib/issues/3058), [#3409](https://github.com/GoogleContainerTools/jib/issues/3409))\n- If `allowInsecureRegistries=true`, HTTP requests are retried on I/O errors only after insecure failover is finalized for each server. ([#3422](https://github.com/GoogleContainerTools/jib/issues/3422))\n\n## 3.1.3\n\n### Added\n\n- Increased robustness in registry communications by retrying HTTP requests (to the effect of retrying image pushes or pulls) on I/O exceptions with exponential backoffs. ([#3351](https://github.com/GoogleContainerTools/jib/pull/3351))\n- Now also supports `username` and `password` properties for the `auths` section in a Docker config (`~/.docker/config.json`). (Previously, only supported was a base64-encoded username and password string of the `auth` property.) ([#3365](https://github.com/GoogleContainerTools/jib/pull/3365))\n\n### Changed\n\n- Upgraded Google HTTP libraries to 1.39.2. ([#3387](https://github.com/GoogleContainerTools/jib/pull/3387))\n\n## 3.1.2\n\n### Fixed\n\n- Fixed the bug introduced in 3.1 that constructs a wrong Java runtime classpath when two dependencies have the same artifact ID and version but different group IDs. The bug occurs only when using Java 9+ or setting `jib.container.expandClasspathDependencies`. ([#3331](https://github.com/GoogleContainerTools/jib/pull/3331))\n\n## 3.1.1\n\n### Fixed\n\n- Fixed the regression introduced in 3.1.0 where a build may fail due to an error from main class inference even if `jib.container.entrypoint` is configured. ([#3295](https://github.com/GoogleContainerTools/jib/pull/3295))\n\n## 3.1.0\n\n### Added\n\n- For Google Artifact Registry (`*-docker.pkg.dev`), Jib now tries [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) last like it has been doing for `gcr.io`. ([#3241](https://github.com/GoogleContainerTools/jib/pull/3241))\n- Added lazy evaluation for `jib.container.labels` using Gradle Property and Provider. ([#3242](https://github.com/GoogleContainerTools/jib/issues/3242))\n\n### Changed\n\n- Jib now creates an additional layer that contains two small text files: [`/app/jib-classpath-file` and `/app/jib-main-class-file`](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin/README.md#custom-container-entrypoint). They hold, respectively, the final Java runtime classpath and the main class computed by Jib that are suitable for app execution on JVM. For example, with Java 9+, setting the container entrypoint to `java --class-path @/app/jib-classpath-file @/app/jib-main-class-file` will work to start the app. (This is basically the default entrypoint set by Jib when the entrypoint is not explicitly configured by the user.) The files are always generated whether Java 8 or 9+, or whether `jib.container.entrypoint` is explicitly configured. The files can be helpful especially when setting a custom entrypoint for a shell script that needs to get the classpath and the main class computed by Jib, or for [AppCDS](https://github.com/GoogleContainerTools/jib/issues/2471). ([#3280](https://github.com/GoogleContainerTools/jib/pull/3280))\n- For Java 9+ apps, the default Java runtime classpath explicitly lists all the app dependencies, preserving the dependency loading order declared by Gradle. This is done by changing the default entrypoint to use the new classpath JVM argument file (basically `java -cp @/app/jib-classpath-file`). As such, `jib.container.expandClasspathDependencies` takes no effect for Java 9+. ([#3280](https://github.com/GoogleContainerTools/jib/pull/3280))\n- Timestamps of file entries in a tarball built with `jibBuildTar` are set to the epoch, making the tarball reproducible. ([#3158](https://github.com/GoogleContainerTools/jib/issues/3158))\n\n## 3.0.0\n\n### Added\n\n- New `includes` and `excludes` options for `jib.extraDirectories`. This enables copying a subset of files from the source directory using glob patterns. ([#2564](https://github.com/GoogleContainerTools/jib/issues/2564))\n- Added an option `configurationName` to specify the name of the [Gradle Configuration](https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html) to use. The option can be lazily configured, for example, using Gradle `Provider` or `Property`. ([#3034](https://github.com/GoogleContainerTools/jib/pull/3034))\n```gradle\n    jib {\n      configurationName = 'myconfig'\n    }\n```\n\n### Changed\n\n- [Switched the default base images](https://github.com/GoogleContainerTools/jib/blob/master/docs/default_base_image.md) from Distroless to [`adoptopenjdk:{8,11}-jre`](https://hub.docker.com/_/adoptopenjdk) and [`jetty`](https://hub.docker.com/_/jetty) (for WAR). ([#3124](https://github.com/GoogleContainerTools/jib/pull/3124))\n\n### Fixed\n\n- Fixed an issue where some log messages used color in the \"plain\" console output. ([#2764](https://github.com/GoogleContainerTools/jib/pull/2764))\n\n## 2.8.0\n\n### Added\n\n- Added support for [configuring registry mirrors](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#i-am-hitting-docker-hub-rate-limits-how-can-i-configure-registry-mirrors) for base images. This is useful when hitting [Docker Hub rate limits](https://www.docker.com/increase-rate-limits). Only public mirrors (such as `mirror.gcr.io`) are supported. ([#3011](https://github.com/GoogleContainerTools/jib/issues/3011))\n\n### Changed\n\n- Build will fail if Jib cannot create or read the [global Jib configuration file](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#global-jib-configuration). ([#2996](https://github.com/GoogleContainerTools/jib/pull/2996))\n\n## 2.7.1\n\n### Fixed\n\n- Updated jackson dependency version causing compatibility issues. ([#2931](https://github.com/GoogleContainerTools/jib/issues/2931))\n- Deprecated use of `@Optional` on boolean attribute ([#2930](https://github.com/GoogleContainerTools/jib/issues/2930))\n\n## 2.7.0\n\n### Added\n\n- Added an option `jib.container.expandClasspathDependencies` to preserve the order of loading dependencies as configured in a project. The option enumerates dependency JARs instead of using a wildcard (`/app/libs/*`) in the Java runtime classpath for an image entrypoint. ([#1871](https://github.com/GoogleContainerTools/jib/issues/1871), [#1907](https://github.com/GoogleContainerTools/jib/issues/1907), [#2228](https://github.com/GoogleContainerTools/jib/issues/2228), [#2733](https://github.com/GoogleContainerTools/jib/issues/2733))\n    - The option is also useful for AppCDS. ([#2471](https://github.com/GoogleContainerTools/jib/issues/2471))\n    - Turning on the option may result in a very long classpath string, and the OS may not support passing such a long string to JVM.\n- Added lazy evaluation for `jib.(to|from).auth.(username|password)` and `jib.from.image` using Gradle Property and Provider. ([#2905](https://github.com/GoogleContainerTools/jib/issues/2905))\n\n### Fixed\n\n- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/issues/2819))\n- Fixed build failure when using a Docker daemon base image (`docker://...`) that has duplicate layers. ([#2829](https://github.com/GoogleContainerTools/jib/issues/2829))\n\n## 2.6.0\n\n### Added\n\n- Added lazy evaluation for `jib.to.image` and `jib.to.tags` using Gradle Property and Provider. ([#2727](https://github.com/GoogleContainerTools/jib/issues/2727))\n- _Incubating feature_: can now configure multiple platforms (such as architectures) to build multiple images as a bundle and push as a manifest list (also known as a fat manifest). As an incubating feature, there are certain limitations. For example, OCI image indices are not supported, and building a manifest list is supported only for registry pushing (the `jib` task). ([#2523](https://github.com/GoogleContainerTools/jib/issues/2523))\n   ```gradle\n   jib.from {\n     image = '... image reference to a manifest list ...'\n     platforms {\n       platform {\n         architecture = 'arm64'\n         os = 'linux'\n       }\n     }\n   }\n   ```\n\n### Changed\n\n- Previous locally cached base image manifests will be ignored, as the caching mechanism changed to enable multi-platform image building. ([#2730](https://github.com/GoogleContainerTools/jib/pull/2730), [#2711](https://github.com/GoogleContainerTools/jib/pull/2711))\n- Upgraded the ASM library to 9.0 to resolve an issue when auto-inferring main class in Java 15+. ([#2776](https://github.com/GoogleContainerTools/jib/pull/2776))\n\n### Fixed\n\n- Fixed `NullPointerException` during input validation (in Java 9+) when configuring Jib parameters using certain immutable collections (such as `List.of()`). ([#2702](https://github.com/GoogleContainerTools/jib/issues/2702))\n- Fixed authentication failure with Azure Container Registry when using [\"tokens\"](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-repository-scoped-permissions). ([#2784](https://github.com/GoogleContainerTools/jib/issues/2784))\n- Improved authentication flow for base image registry. ([#2134](https://github.com/GoogleContainerTools/jib/issues/2134))\n- Throw `IllegalArgumentException` with an error message instead of throwing a `NullPointerException` when `jib.to.tags` is set to a collection containing a `null` value. ([#2760](https://github.com/GoogleContainerTools/jib/issues/2760))\n\n## 2.5.1\n\n### Fixed\n\n- Fixed an issue that configuring `jib.from.platforms` was always additive to the default `amd64/linux` platform. ([#2783](https://github.com/GoogleContainerTools/jib/issues/2783))\n\n## 2.5.0\n\n### Added\n\n- Also tries `.exe` file extension for credential helpers on Windows. ([#2527](https://github.com/GoogleContainerTools/jib/issues/2527))\n- New system property `jib.skipExistingImages` (false by default) to skip pushing images (manifests) if the image already exists in the registry. ([#2360](https://github.com/GoogleContainerTools/jib/issues/2360))\n- _Incubating feature_: can now configure desired platform (architecture and OS) to select the matching manifest from a Docker manifest list. Currently supports building only one image. OCI image indices are not supported. ([#1567](https://github.com/GoogleContainerTools/jib/issues/1567))\n   ```gradle\n   jib.from {\n     image = '... image reference to a manifest list ...'\n     platforms {\n       platform {\n         architecture = 'arm64'\n         os = 'linux'\n       }\n     }\n   }\n   ```\n\n### Fixed\n\n- Fixed reporting a wrong credential helper name when the helper does not exist on Windows. ([#2527](https://github.com/GoogleContainerTools/jib/issues/2527))\n- Fixed `NullPointerException` when the `\"auths\":` section in `~/.docker/config.json` has an entry with no `\"auth\":` field. ([#2535](https://github.com/GoogleContainerTools/jib/issues/2535))\n- Fixed `NullPointerException` to return a helpful message when a server does not provide any message in certain error cases (400 Bad Request, 404 Not Found, and 405 Method Not Allowed). ([#2532](https://github.com/GoogleContainerTools/jib/issues/2532))\n- Now supports sending client certificate (for example, via the `javax.net.ssl.keyStore` and `javax.net.ssl.keyStorePassword` system properties) and thus enabling mutual TLS authentication. ([#2585](https://github.com/GoogleContainerTools/jib/issues/2585), [#2226](https://github.com/GoogleContainerTools/jib/issues/2226))\n- Fixed an issue where Jib cannot infer Kotlin main class that takes no arguments. ([#2666](https://github.com/GoogleContainerTools/jib/pull/2666))\n\n## 2.4.0\n\n### Added\n\n- Jib Extension Framework! The framework enables anyone to easily extend and tailor the Jib Gradle plugin behavior to their liking. Check out the new [Jib Extensions](https://github.com/GoogleContainerTools/jib-extensions) GitHub repository to learn more. ([#2401](https://github.com/GoogleContainerTools/jib/issues/2401))\n- Project dependencies in a multi-module WAR project are now stored in a separate \"project dependencies\" layer (as currently done for a non-WAR project). ([#2450](https://github.com/GoogleContainerTools/jib/issues/2450))\n\n### Changed\n\n- Previous locally cached application layers (`<project root>/target/jib-cache`) will be ignored because of changes to the caching selectors. ([#2499](https://github.com/GoogleContainerTools/jib/pull/2499))\n\n### Fixed\n\n- Fixed authentication failure with Azure Container Registry when using an identity token defined in the `auths` section of Docker config (`~/.docker/config.json`). ([#2488](https://github.com/GoogleContainerTools/jib/pull/2488))\n\n## 2.3.0\n\n### Added\n\n- `jib.extraDirectories.paths` closure to allow configuring the source and target of an extra directory. ([#1581](https://github.com/GoogleContainerTools/jib/issues/1581))\n\n### Fixed\n\n- Fixed the problem not inheriting `USER` container configuration from a base image. ([#2421](https://github.com/GoogleContainerTools/jib/pull/2421))\n- Fixed wrong capitalization of JSON properties in a loadable Docker manifest when building a tar image. ([#2430](https://github.com/GoogleContainerTools/jib/issues/2430))\n- Fixed an issue when using a base image whose image creation timestamp contains timezone offset. ([#2428](https://github.com/GoogleContainerTools/jib/issues/2428))\n- Fixed an issue inferring a wrong main class or using an invalid main class (for example, Spring Boot project containing multiple main classes). ([#2456](https://github.com/GoogleContainerTools/jib/issues/2456))\n\n## 2.2.0\n\n### Added\n\n- Glob pattern support for `jib.extraDirectories.permissions`. ([#1200](https://github.com/GoogleContainerTools/jib/issues/1200))\n- Support for image references with both a tag and a digest. ([#1481](https://github.com/GoogleContainerTools/jib/issues/1481))\n- The `DOCKER_CONFIG` environment variable specifying the directory containing docker configs is now checked during credential retrieval. ([#1618](https://github.com/GoogleContainerTools/jib/issues/1618))\n- Also tries `.cmd` file extension for credential helpers on Windows. ([#2399](https://github.com/GoogleContainerTools/jib/issues/2399))\n\n### Changed\n\n- `jib.container.creationTime` now accepts more timezone formats:`+HHmm`. This allows for easier configuration of creationTime by external systems. ([#2320](https://github.com/GoogleContainerTools/jib/issues/2320))\n\n## 2.1.0\n\n### Added\n\n- Additionally reads credentials from `~/.docker/.dockerconfigjson` and legacy Docker config (`~/.docker/.dockercfg`). Also searches for `$HOME/.docker/*` (in addition to current `System.get(\"user.home\")/.docker/*`). This may help retrieve credentials, for example, on Kubernetes. ([#2260](https://github.com/GoogleContainerTools/jib/issues/2260))\n- New skaffold configuration options that modify how jib's build config is presented to skaffold ([#2292](https://github.com/GoogleContainerTools/jib/pull/2292)):\n    - `jib.skaffold.watch.buildIncludes`: a list of build files to watch\n    - `jib.skaffold.watch.includes`: a list of project files to watch\n    - `jib.skaffold.watch.excludes`: a list of files to exclude from watching\n    - `jib.skaffold.sync.excludes`: a list of files to exclude from sync'ing\n\n### Fixed\n\n- Fixed authentication failure with error `server did not return 'WWW-Authenticate: Bearer' header` in certain cases (for example, on OpenShift). ([#2258](https://github.com/GoogleContainerTools/jib/issues/2258))\n- Fixed an issue where using local Docker images (by `docker://...`) on Windows caused an error. ([#2270](https://github.com/GoogleContainerTools/jib/issues/2270))\n- Fixed build failures with Skaffold when the Gradle Java Platform plugin is applied. ([#2269](https://github.com/GoogleContainerTools/jib/issues/2269))\n- For Spring Boot projects using `containerizingMode = 'packaged'`, Jib now overrides `archiveClassifier` of the `jar` task only when safe and necessary. ([#2278](https://github.com/GoogleContainerTools/jib/issues/2278))\n- Fixed an issue where user-configured task dependencies for the Jib task is overwritten and thus ineffective. ([#2289](https://github.com/GoogleContainerTools/jib/pull/2289))\n\n## 2.0.0\n\n### Added\n\n- Added json output file for image metadata after a build is complete. Writes to `build/jib-image.json` by default, configurable with `jib.outputPaths.imageJson`. ([#2227](https://github.com/GoogleContainerTools/jib/pull/2227))\n- Added automatic update checks. Jib will now display a message if there is a new version of Jib available. See the [privacy page](../docs/privacy.md) for more details. ([#2193](https://github.com/GoogleContainerTools/jib/issues/2193))\n\n### Changed\n\n- Removed `jibDockerBuild.dockerClient` in favor of `jib.dockerClient`. ([#1983](https://github.com/GoogleContainerTools/jib/issues/1983))\n- Removed deprecated `jib.extraDirectory` configuration in favor of `jib.extraDirectories`. ([#1691](https://github.com/GoogleContainerTools/jib/issues/1691))\n- Removed deprecated `jib.container.useCurrentTimestamp` configuration in favor of `jib.container.creationTime` with `USE_CURRENT_TIMESTAMP`. ([#1897](https://github.com/GoogleContainerTools/jib/issues/1897))\n- HTTP redirection URLs are no longer sanitized in order to work around an issue with certain registries that do not conform to HTTP standards. This resolves an issue with using Red Hat OpenShift and Quay registries. ([#2106](https://github.com/GoogleContainerTools/jib/issues/2106), [#1986](https://github.com/GoogleContainerTools/jib/issues/1986#issuecomment-547610104))\n- Requires Gradle 5.1 or newer (up from 4.9).\n- The default base image cache location has been changed on MacOS and Windows. ([#2216](https://github.com/GoogleContainerTools/jib/issues/2216))\n    - MacOS (`$XDG_CACHE_HOME` defined): from `$XDG_CACHE_HOME/google-cloud-tools-java/jib/` to `$XDG_CACHE_HOME/Google/Jib/`\n    - MacOS (`$XDG_CACHE_HOME` not defined): from `$HOME/Library/Application Support/google-cloud-tools-java/jib/` to `$HOME/Library/Caches/Google/Jib/`\n    - Windows (`$XDG_CACHE_HOME` defined): from `$XDG_CACHE_HOME\\google-cloud-tools-java\\jib\\` to `$XDG_CACHE_HOME\\Google\\Jib\\Cache\\`\n    - Windows (`$XDG_CACHE_HOME` not defined): from `%LOCALAPPDATA%\\google-cloud-tools-java\\jib\\` to `%LOCALAPPDATA%\\Google\\Jib\\Cache\\`\n    - Initial builds will be slower until the cache is repopulated, unless you manually move the cache from the old location to the new location\n\n### Fixed\n\n- `jibBuildTar` with `jib.container.format='OCI'` now builds a correctly formatted OCI archive. ([#2124](https://github.com/GoogleContainerTools/jib/issues/2124))\n- Now `jib.containerizingMode='packaged'` works as intended with Spring Boot projects that generate a fat JAR. ([#2178](https://github.com/GoogleContainerTools/jib/pull/2178))\n- Now automatically refreshes Docker registry authentication tokens when expired, fixing the issue that long-running builds may fail with \"401 unauthorized.\" ([#691](https://github.com/GoogleContainerTools/jib/issues/691))\n\n## 1.8.0\n\n### Changed\n\n- Requires Gradle 4.9 or newer (up from 4.6).\n- Optimized building to a registry with local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))\n\n### Fixed\n\n- Fixed reporting parent build file when `skaffold init` is run on multi-module projects. ([#2091](https://github.com/GoogleContainerTools/jib/pull/2091))\n- Now correctly uses the `war` task if it is enabled and the `bootWar` task is disabled for Spring WAR projects. ([#2096](https://github.com/GoogleContainerTools/jib/issues/2096))\n- `allowInsecureRegistries` and the `sendCredentialsOverHttp` system property are now effective for authentication service server connections. ([#2074](https://github.com/GoogleContainerTools/jib/pull/2074))\n- Fixed inefficient communications when interacting with insecure registries and servers (when `allowInsecureRegistries` is set). ([#946](https://github.com/GoogleContainerTools/jib/issues/946))\n\n## 1.7.0\n\n### Added\n\n- `jib.outputPaths` object for configuration output file locations ([#1561](https://github.com/GoogleContainerTools/jib/issues/1561))\n  - `jib.outputPaths.tar` configures output path of `jibBuildTar` (`build/jib-image.tar` by default)\n  - `jib.outputPaths.digest` configures the output path of the image digest (`build/jib-image.digest` by default)\n  - `jib.outputPaths.imageId` configures output path of the image id  (`build/jib-image.id` by default)\n- Main class inference support for Java 13/14. ([#2015](https://github.com/GoogleContainerTools/jib/issues/2015))\n\n### Changed\n\n- Local base image layers are now processed in parallel, speeding up builds using large local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))\n- The base image manifest is no longer pulled from the registry if a digest is provided and the manifest is already cached. ([#1881](https://github.com/GoogleContainerTools/jib/issues/1881))\n- Docker daemon base images are now cached more effectively, speeding up builds using `docker://` base images. ([#1912](https://github.com/GoogleContainerTools/jib/issues/1912))\n\n### Fixed\n\n- Fixed temporary directory cleanup during builds using local base images. ([#2016](https://github.com/GoogleContainerTools/jib/issues/2016))\n- Fixed additional tags being ignored when building to a tarball. ([#2043](https://github.com/GoogleContainerTools/jib/issues/2043))\n- Fixed `tar://` base image failing if tar does not contain explicit directory entries. ([#2067](https://github.com/GoogleContainerTools/jib/issues/2067))\n\n## 1.6.1\n\n### Fixed\n\n- Fixed an issue with using custom base images in Java 12+ projects. ([#1995](https://github.com/GoogleContainerTools/jib/issues/1995))\n\n## 1.6.0\n\n### Added\n\n- Support for local base images by prefixing `jib.from.image` with `docker://` to build from a docker daemon image, or `tar://` to build from a tarball image. ([#1468](https://github.com/GoogleContainerTools/jib/issues/1468), [#1905](https://github.com/GoogleContainerTools/jib/issues/1905))\n\n### Changed\n\n- To disable parallel execution, the property `jib.serialize` should be used instead of `jibSerialize`. ([#1968](https://github.com/GoogleContainerTools/jib/issues/1968))\n- For retrieving credentials from Docker config (`~/.docker/config.json`), `credHelpers` now takes precedence over `credsStore`, followed by `auths`. ([#1958](https://github.com/GoogleContainerTools/jib/pull/1958))\n- The legacy `credsStore` no longer requires defining empty registry entries in `auths` to be used. This now means that if `credsStore` is defined, `auths` will be completely ignored. ([#1958](https://github.com/GoogleContainerTools/jib/pull/1958))\n- `jib.dockerClient` is now configurable on all tasks, not just `jibDockerBuild`. ([#1932](https://github.com/GoogleContainerTools/jib/issues/1932))\n- `jibDockerBuild.dockerClient` is deprecated in favor of `jib.dockerClient`.\n\n### Fixed\n\n- Fixed the regression of slow network operations introduced at 1.5.0. ([#1980](https://github.com/GoogleContainerTools/jib/pull/1980))\n- Fixed an issue where connection timeout sometimes fell back to attempting plain HTTP (non-HTTPS) requests when `allowInsecureRegistries` is set. ([#1949](https://github.com/GoogleContainerTools/jib/pull/1949))\n\n## 1.5.1\n\n### Fixed\n\n- Fixed an issue interacting with certain registries due to changes to URL handling in the underlying Apache HttpClient library. ([#1924](https://github.com/GoogleContainerTools/jib/issues/1924))\n\n## 1.5.0\n\n### Added\n\n- Can now set timestamps (last modified time) of the files in the built image with `jib.container.filesModificationTime`. The value should either be `EPOCH_PLUS_SECOND` to set the timestamps to Epoch + 1 second (default behavior), or an ISO 8601 date time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`. ([#1818](https://github.com/GoogleContainerTools/jib/pull/1818))\n- Can now set container creation timestamp with `jib.container.creationTime`. The value should be `EPOCH`, `USE_CURRENT_TIMESTAMP`, or an ISO 8601 date time. ([#1609](https://github.com/GoogleContainerTools/jib/issues/1609))\n- For Google Container Registry (gcr.io), Jib now tries [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) (ADC) last when no credentials can be retrieved. ADC are available on many Google Cloud Platform (GCP) environments (such as Google Cloud Build, Google Compute Engine, Google Kubernetes Engine, and Google App Engine). Application Default Credentials can also be configured with `gcloud auth application-default login` locally or through the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. ([#1902](https://github.com/GoogleContainerTools/jib/pull/1902))\n\n### Changed\n\n- When building to a registry, Jib now skips downloading and caching base image layers that already exist in the target registry. This feature will be particularly useful in CI/CD environments. However, if you want to force caching base image layers locally, set the system property `-Djib.alwaysCacheBaseImage=true`. ([#1840](https://github.com/GoogleContainerTools/jib/pull/1840))\n- `jib.container.useCurrentTimestamp` has been deprecated in favor of `jib.container.creationTime` with `USE_CURRENT_TIMESTAMP`. ([#1609](https://github.com/GoogleContainerTools/jib/issues/1609))\n\n## 1.4.0\n\n### Added\n\n- Can now containerize a JAR artifact instead of putting individual `.class` and resource files with `jib.containerizingMode = 'packaged'`. ([#1760](https://github.com/GoogleContainerTools/jib/pull/1760/files))\n- Now automatically supports WAR created by the Spring Boot Gradle Plugin via the `bootWar` task. ([#1786](https://github.com/GoogleContainerTools/jib/issues/1786))\n- Can now use `jib.from.image = 'scratch'` to use the scratch (empty) base image for builds. ([#1794](https://github.com/GoogleContainerTools/jib/pull/1794/files))\n\n### Changed\n\n- Dependencies are now split into three layers: dependencies, snapshots dependencies, project dependencies. ([#1724](https://github.com/GoogleContainerTools/jib/pull/1724))\n\n### Fixed\n\n- Re-enabled cross-repository blob mounts. ([#1793](https://github.com/GoogleContainerTools/jib/pull/1793))\n- Manifest lists referenced directly by sha256 are automatically parsed and the first `linux/amd64` manifest is used. ([#1811](https://github.com/GoogleContainerTools/jib/issues/1811))\n\n## 1.3.0\n\n### Changed\n\n- Docker credentials (`~/.docker/config.json`) are now given priority over registry-based inferred credential helpers. ([#1704](https://github.com/GoogleContainerTools/jib/pulls/1704))\n\n### Fixed\n\n- Fixed an issue with `jibBuildTar` where `UP-TO-DATE` checks were incorrect. ([#1757](https://github.com/GoogleContainerTools/jib/issues/1757))\n\n## 1.2.0\n\n### Added\n\n- Container configurations in the base image are now propagated when registry uses the old V2 image manifest, schema version 1 (such as Quay). ([#1641](https://github.com/GoogleContainerTools/jib/issues/1641))\n- Can now prepend paths in the container to the computed classpath with `jib.container.extraClasspath`. ([#1642](https://github.com/GoogleContainerTools/jib/pull/1642))\n- Can now build in offline mode using `--offline`. ([#718](https://github.com/GoogleContainerTools/jib/issues/718))\n- Now supports multiple extra directories with `jib.extraDirectories.{paths|.permissions}`. ([#1020](https://github.com/GoogleContainerTools/jib/issues/1020))\n\n### Changed\n\n- `jib.extraDirectory({.path|.permissions})` are deprecated in favor of the new `jib.extraDirectories.{paths|.permissions}` configurations. ([#1671](https://github.com/GoogleContainerTools/jib/pull/1671))\n\n### Fixed\n\n- Labels in the base image are now propagated. ([#1643](https://github.com/GoogleContainerTools/jib/issues/1643))\n- Fixed an issue with using OCI base images. ([#1683](https://github.com/GoogleContainerTools/jib/issues/1683))\n\n## 1.1.2\n\n### Fixed\n\n- Fixed an issue where automatically generated parent directories in a layer did not get their timestamp configured correctly to epoch + 1s. ([#1648](https://github.com/GoogleContainerTools/jib/issues/1648))\n\n## 1.1.1\n\n### Fixed\n\n- Fixed an issue where the plugin creates wrong images by adding base image layers in reverse order when registry uses the old V2 image manifest, schema version 1 (such as Quay). ([#1627](https://github.com/GoogleContainerTools/jib/issues/1627))\n\n## 1.1.0\n\n### Changed\n\n- `os` and `architecture` are taken from base image. ([#1564](https://github.com/GoogleContainerTools/jib/pull/1564))\n\n### Fixed\n\n- Fixed an issue where pushing to Docker Hub fails when the host part of an image reference is `docker.io`. ([#1549](https://github.com/GoogleContainerTools/jib/issues/1549))\n\n## 1.0.2\n\n### Added\n\n- Java 9+ WAR projects are now supported and run on the distroless Jetty Java 11 image (https://github.com/GoogleContainerTools/distroless) by default. Java 8 projects remain on the distroless Jetty Java 8 image. ([#1510](https://github.com/GoogleContainerTools/jib/issues/1510))\n- Now supports authentication against Azure Container Registry using `docker-credential-acr-*` credential helpers. ([#1490](https://github.com/GoogleContainerTools/jib/issues/1490))\n\n### Fixed\n\n- Fixed an issue where setting `allowInsecureRegistries` may fail to try HTTP. ([#1517](https://github.com/GoogleContainerTools/jib/issues/1517))\n- Crash on talking to servers that do not set the `Content-Length` HTTP header or send an incorrect value. ([#1512](https://github.com/GoogleContainerTools/jib/issues/1512))\n\n## 1.0.1\n\n### Added\n\n- Java 9+ projects are now supported and run on the distroless Java 11 image (https://github.com/GoogleContainerTools/distroless) by default. Java 8 projects remain on the distroless Java 8 image. ([#1279](https://github.com/GoogleContainerTools/jib/issues/1279))\n\n### Fixed\n\n- Failure to infer main class when main method is defined using varargs (i.e. `public static void main(String... args)`). ([#1456](https://github.com/GoogleContainerTools/jib/issues/1456))\n\n## 1.0.0\n\n### Changed\n\n- Shortened progress bar display - make sure console window is at least 50 characters wide or progress bar display can be messy. ([#1361](https://github.com/GoogleContainerTools/jib/issues/1361))\n\n## 1.0.0-rc2\n\n### Added\n\n- Setting proxy credentials (via system properties `http(s).proxyUser` and `http(s).proxyPassword`) is now supported.\n\n### Changed\n\n- Java 9+ projects using the default distroless Java 8 base image will now fail to build. ([#1143](https://github.com/GoogleContainerTools/jib/issues/1143))\n\n## 1.0.0-rc1\n\n### Added\n\n- `jib.baseImageCache` and `jib.applicationCache` system properties for setting cache directories. ([#1238](https://github.com/GoogleContainerTools/jib/issues/1238))\n- Build progress shown via a progress bar - set `-Djib.console=plain` to show progress as log messages. ([#1297](https://github.com/GoogleContainerTools/jib/issues/1297))\n\n### Changed\n\n- Removed `jib.useOnlyProjectCache` parameter in favor of the `jib.useOnlyProjectCache` system property. ([#1308](https://github.com/GoogleContainerTools/jib/issues/1308))\n\n### Fixed\n\n- Builds failing due to dependency JARs with the same name. ([#810](https://github.com/GoogleContainerTools/jib/issues/810))\n\n## 0.10.1\n\n### Added\n\n- Image ID is now written to `build/jib-image.id`. ([#1204](https://github.com/GoogleContainerTools/jib/issues/1204))\n- `jib.container.entrypoint = 'INHERIT'` allows inheriting `ENTRYPOINT` and `CMD` from the base image. While inheriting `ENTRYPOINT`, you can also override `CMD` using `jib.container.args`.\n- `container.workingDirectory` configuration parameter to set the working directory. ([#1225](https://github.com/GoogleContainerTools/jib/issues/1225))\n- Adds support for configuring volumes. ([#1121](https://github.com/GoogleContainerTools/jib/issues/1121))\n- Exposed ports are now propagated from the base image. ([#595](https://github.com/GoogleContainerTools/jib/issues/595))\n- Docker health check is now propagated from the base image. ([#595](https://github.com/GoogleContainerTools/jib/issues/595))\n\n### Changed\n\n- Removed `jibExportDockerContext` task. ([#1219](https://github.com/GoogleContainerTools/jib/issues/1219))\n\n### Fixed\n\n- NullPointerException thrown with incomplete `auth` configuration. ([#1177](https://github.com/GoogleContainerTools/jib/issues/1177))\n\n## 0.10.0\n\n### Added\n\n- Properties for each configuration parameter, allowing any parameter to be set via commandline. ([#1083](https://github.com/GoogleContainerTools/jib/issues/1083))\n- `jib.to.credHelper` and `jib.from.credHelper` can be used to specify a credential helper suffix or a full path to a credential helper executable. ([#925](https://github.com/GoogleContainerTools/jib/issues/925))\n- `container.user` configuration parameter to configure the user and group to run the container as. ([#1029](https://github.com/GoogleContainerTools/jib/issues/1029))\n- Preliminary support for building images for WAR projects. ([#431](https://github.com/GoogleContainerTools/jib/issues/431))\n- `jib.extraDirectory` closure with a `path` and `permissions` field. ([#794](https://github.com/GoogleContainerTools/jib/issues/794))\n  - `jib.extraDirectory.path` configures the extra layer directory (still also configurable via `jib.extraDirectory = file(...)`)\n  - `jib.extraDirectory.permissions` is a map from absolute path on container to the file's permission bits (represented as an octal string).\n- Image digest is now written to `build/jib-image.digest`. ([#933](https://github.com/GoogleContainerTools/jib/issues/933))\n- Adds the layer type to the layer history as comments. ([#1198](https://github.com/GoogleContainerTools/jib/issues/1198))\n- `jibDockerBuild.dockerClient.executable` and `jibDockerBuild.dockerClient.environment` to set Docker client binary path (defaulting to `docker`) and additional environment variables to apply when running the binary. ([#1214](https://github.com/GoogleContainerTools/jib/pull/1214))\n\n### Changed\n\n- Removed deprecated `jib.jvmFlags`, `jib.mainClass`, `jib.args`, and `jib.format` in favor of the equivalents under `jib.container`. ([#461](https://github.com/GoogleContainerTools/jib/issues/461))\n- `jibExportDockerContext` generates different directory layout and `Dockerfile` to enable WAR support. ([#1007](https://github.com/GoogleContainerTools/jib/pull/1007))\n- File timestamps in the built image are set to 1 second since the epoch (hence 1970-01-01T00:00:01Z) to resolve compatibility with applications on Java 6 or below where the epoch means nonexistent or I/O errors; previously they were set to the epoch. ([#1079](https://github.com/GoogleContainerTools/jib/issues/1079))\n- Sets tag to \"latest\" instead of \"unspecified\" if `jib.to.image` and project version are both unspecified when running `jibDockerBuild` or `jibBuildTar`. ([#1096](https://github.com/GoogleContainerTools/jib/issues/1096))\n\n## 0.9.13\n\n### Fixed\n\n- Adds environment variable configuration to Docker context generator. ([#890 (comment)](https://github.com/GoogleContainerTools/jib/issues/890#issuecomment-430227555))\n\n## 0.9.12\n\n### Fixed\n\n- `Cannot access 'image': it is public in <anonymous>` error. ([#1060](https://github.com/GoogleContainerTools/jib/issues/1060))\n\n## 0.9.11\n\n### Added\n\n- `container.environment` configuration parameter to configure environment variables. ([#890](https://github.com/GoogleContainerTools/jib/issues/890))\n- `container.appRoot` configuration parameter to configure app root in the image. ([#984](https://github.com/GoogleContainerTools/jib/pull/984))\n- `jib.to.tags` (list) defines additional tags to push to. ([#978](https://github.com/GoogleContainerTools/jib/pull/978))\n\n### Fixed\n\n- Keep duplicate layers to match container history. ([#1017](https://github.com/GoogleContainerTools/jib/pull/1017))\n\n## 0.9.10\n\n### Added\n\n- `container.labels` configuration parameter for configuring labels. ([#751](https://github.com/GoogleContainerTools/jib/issues/751))\n- `container.entrypoint` configuration parameter to set the entrypoint. ([#579](https://github.com/GoogleContainerTools/jib/issues/579))\n- `history` to layer metadata. ([#875](https://github.com/GoogleContainerTools/jib/issues/875))\n- Propagates working directory from the base image. ([#902](https://github.com/GoogleContainerTools/jib/pull/902))\n\n### Fixed\n\n- Corrects permissions for directories in the container filesystem. ([#772](https://github.com/GoogleContainerTools/jib/pull/772))\n\n## 0.9.9\n\n### Added\n\n- Passthrough labels from base image. ([#750](https://github.com/GoogleContainerTools/jib/pull/750/files))\n\n### Changed\n\n- Reordered classpath in entrypoint to use _resources_, _classes_, and then _dependencies_, to allow dependency patching.\n . ([#777](https://github.com/GoogleContainerTools/jib/issues/777)).  Note that this classpath ordering differs from that used by Gradle's `run` task.\n- Changed logging level of missing build output directory message. ([#677](https://github.com/GoogleContainerTools/jib/issues/677))\n\n### Fixed\n\n- Gradle project dependencies have their `assemble` task run before running a jib task. ([#815](https://github.com/GoogleContainerTools/jib/issues/815))\n\n## 0.9.8\n\n### Added\n\n- Docker context generation now includes snapshot dependencies and extra files. ([#516](https://github.com/GoogleContainerTools/jib/pull/516/files))\n- Disable parallel operation by setting the `jibSerialize` system property to `true`. ([#682](https://github.com/GoogleContainerTools/jib/pull/682))\n\n### Changed\n\n- Propagates environment variables from the base image. ([#716](https://github.com/GoogleContainerTools/jib/pull/716))\n- `allowInsecureRegistries` allows connecting to insecure HTTPS registries (for example, registries using self-signed certificates). ([#733](https://github.com/GoogleContainerTools/jib/pull/733))\n\n### Fixed\n\n- Slow image reference parsing. ([#680](https://github.com/GoogleContainerTools/jib/pull/680))\n- Building empty layers. ([#516](https://github.com/GoogleContainerTools/jib/pull/516/files))\n- Duplicate layer entries causing unbounded cache growth. ([#721](https://github.com/GoogleContainerTools/jib/issues/721))\n- Incorrect authentication error message when target and base registry are the same. ([#758](https://github.com/GoogleContainerTools/jib/issues/758))\n\n## 0.9.7\n\n### Added\n\n- Snapshot dependencies are added as their own layer. ([#584](https://github.com/GoogleContainerTools/jib/pull/584))\n- `jibBuildTar` task to build an image tarball at `build/jib-image.tar`, which can be loaded into docker using `docker load`. ([#514](https://github.com/GoogleContainerTools/jib/issues/514))\n- `container.useCurrentTimestamp` parameter to set the image creation time to the build time. ([#413](https://github.com/GoogleContainerTools/jib/issues/413))\n- Authentication over HTTP using the `sendCredentialsOverHttp` system property. ([#599](https://github.com/GoogleContainerTools/jib/issues/599))\n- HTTP connection and read timeouts for registry interactions configurable with the `jib.httpTimeout` system property. ([#656](https://github.com/GoogleContainerTools/jib/pull/656))\n- Docker context export command-line option `--targetDir` to `--jibTargetDir`. ([#662](https://github.com/GoogleContainerTools/jib/issues/662))\n\n### Changed\n\n- Docker context export command-line option `--targetDir` to `--jibTargetDir`. ([#662](https://github.com/GoogleContainerTools/jib/issues/662))\n\n### Fixed\n\n- Using multi-byte characters in container configuration. ([#626](https://github.com/GoogleContainerTools/jib/issues/626))\n- For Docker Hub, also tries registry aliases when getting a credential from the Docker config. ([#605](https://github.com/GoogleContainerTools/jib/pull/605))\n\n## 0.9.6\n\n### Fixed\n\n- Using a private registry that does token authentication with `allowInsecureRegistries` set to `true`. ([#572](https://github.com/GoogleContainerTools/jib/pull/572))\n\n## 0.9.5\n\n### Added\n\n- Incubating feature to build `src/main/jib` as extra layer in image. ([#562](https://github.com/GoogleContainerTools/jib/pull/562))\n\n## 0.9.4\n\n### Fixed\n\n- Fixed handling case-insensitive `Basic` authentication method. ([#546](https://github.com/GoogleContainerTools/jib/pull/546))\n- Fixed regression that broke pulling base images from registries that required token authentication. ([#549](https://github.com/GoogleContainerTools/jib/pull/549))\n\n## 0.9.3\n\n### Fixed\n\n- Using Docker config for finding registry credentials (was not ignoring extra fields and handling `https` protocol). ([#524](https://github.com/GoogleContainerTools/jib/pull/524))\n\n## 0.9.2\n\n### Added\n\n- Can configure `jibExportDockerContext` output directory with `jibExportDockerContext.targetDir`. ([#492](https://github.com/GoogleContainerTools/jib/pull/492))\n\n### Changed\n\n### Fixed\n\n- Set `jibExportDockerContext` output directory with command line option `--targetDir`. ([#499](https://github.com/GoogleContainerTools/jib/pull/499))\n\n## 0.9.1\n\n### Added\n\n- `container.ports` parameter to define container's exposed ports (similar to Dockerfile `EXPOSE`). ([#383](https://github.com/GoogleContainerTools/jib/issues/383))\n- Can set `allowInsecureRegistries` parameter to `true` to use registries that only support HTTP. ([#388](https://github.com/GoogleContainerTools/jib/issues/388))\n\n### Changed\n\n- Fetches credentials from inferred credential helper before Docker config. ([#401](https://github.com/GoogleContainerTools/jib/issues/401))\n- Container creation date set to timestamp 0. ([#341](https://github.com/GoogleContainerTools/jib/issues/341))\n- Does not authenticate base image pull unless necessary - reduces build time by about 500ms. ([#414](https://github.com/GoogleContainerTools/jib/pull/414))\n- `jvmFlags`, `mainClass`, `args`, and `format` are now grouped under `container` configuration object. ([#384](https://github.com/GoogleContainerTools/jib/issues/384))\n- Warns instead of errors when classes not found. ([#462](https://github.com/GoogleContainerTools/jib/pull/462))\n\n### Fixed\n\n- Using Azure Container Registry now works - define credentials in `jib.to.auth`/`jib.from.auth`. ([#415](https://github.com/GoogleContainerTools/jib/issues/415))\n- Supports `access_token` as alias to `token` in registry authentication. ([#420](https://github.com/GoogleContainerTools/jib/pull/420))\n- Docker context export for Groovy project. ([#459](https://github.com/GoogleContainerTools/jib/pull/459))\n- Visibility of `jib.to.image`. ([#460](https://github.com/GoogleContainerTools/jib/pull/460))\n\n## 0.9.0\n\n### Added\n\n- Export a Docker context (including a Dockerfile) with `jibExportDockerContext`. ([#204](https://github.com/google/jib/issues/204))\n- Warns if build may not be reproducible. ([#245](https://github.com/GoogleContainerTools/jib/pull/245))\n- `jibDockerBuild` gradle task to build straight to Docker daemon. ([#265](https://github.com/GoogleContainerTools/jib/pull/265))\n- `mainClass` is inferred by searching through class files if configuration is missing. ([#278](https://github.com/GoogleContainerTools/jib/pull/278))\n- All tasks depend on `classes` by default. ([#335](https://github.com/GoogleContainerTools/jib/issues/335))\n- Can now specify target image with `--image`. ([#328](https://github.com/GoogleContainerTools/jib/issues/328))\n- `args` parameter to define default main arguments. ([#346](https://github.com/GoogleContainerTools/jib/issues/346))\n\n### Changed\n\n- Removed `reproducible` parameter - application layers will always be reproducible. ([#245](https://github.com/GoogleContainerTools/jib/pull/245))\n\n### Fixed\n\n- Using base images that lack entrypoints. ([#284](https://github.com/GoogleContainerTools/jib/pull/284))\n\n## 0.1.1\n\n### Added\n\n- Warns if specified `mainClass` is not a valid Java class. ([#206](https://github.com/google/jib/issues/206))\n- Can specify registry credentials to use directly with `from.auth` and `to.auth`. ([#215](https://github.com/google/jib/issues/215))\n"
  },
  {
    "path": "jib-gradle-plugin/README.md",
    "content": "![stable](https://img.shields.io/badge/stability-stable-brightgreen.svg)\n[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/google/cloud/tools/jib/com.google.cloud.tools.jib.gradle.plugin/maven-metadata.xml.svg?colorB=007ec6&label=gradle)](https://plugins.gradle.org/plugin/com.google.cloud.tools.jib)\n[![Gitter version](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/google/jib)\n\n# Jib - Containerize your Gradle Java project\n\nJib is a [Gradle](https://gradle.org/) plugin for building Docker and [OCI](https://github.com/opencontainers/image-spec) images for your Java applications.\n\nFor the Maven plugin, see the [jib-maven-plugin project](../jib-maven-plugin).\n\nFor information about the project, see the [Jib project README](../README.md).\n\n| ☑️  Jib User Survey |\n| :----- |\n| What do you like best about Jib? What needs to be improved? Please tell us by taking a [one-minute survey](https://forms.gle/YRFeamGj51xmgnx28). Your responses will help us understand Jib usage and allow us to serve our customers (you!) better. |\n\n## Table of Contents\n\n* [Upcoming Features](#upcoming-features)\n* [Quickstart](#quickstart)\n  * [Setup](#setup)\n  * [Configuration](#configuration)\n  * [Build your image](#build-your-image)\n    * [Build to Docker Daemon](#build-to-docker-daemon)\n    * [Build an image tarball](#build-an-image-tarball)\n  * [Run `jib` with each build](#run-jib-with-each-build)\n  * [Additional Build Artifacts](#additional-build-artifacts)\n* [Multi Module Projects](#multi-module-projects)\n* [Extended Usage](#extended-usage)\n  * [System Properties](#system-properties)\n  * [Global Jib Configuration](#global-jib-configuration)\n  * [Example](#example)\n  * [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image)\n  * [Authentication Methods](#authentication-methods)\n    * [Using Docker configuration files](#using-docker-configuration-files)\n    * [Using Docker Credential Helpers](#using-docker-credential-helpers)\n    * [Using Specific Credentials](#using-specific-credentials)\n  * [Custom Container Entrypoint](#custom-container-entrypoint)\n  * [Reproducible Build Timestamps](#reproducible-build-timestamps)\n  * [Jib Extensions](#jib-extensions)\n  * [WAR Projects](#war-projects)\n  * [Skaffold Integration](#skaffold-integration)\n* [Need Help?](#need-help)\n* [Community](#community)\n\n## Quickstart\n\n### Setup\n\n*Make sure you are using Gradle version 5.1 or later.*\n\nIn your Gradle Java project, add the plugin to your `build.gradle`:\n\n```groovy\nplugins {\n  id 'com.google.cloud.tools.jib' version '3.5.3'\n}\n```\n\n*See the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/com.google.cloud.tools.jib) for more details.*\n\nYou can containerize your application easily with one command:\n\n```shell\ngradle jib --image=<MY IMAGE>\n```\n\nThis builds and pushes a container image for your application to a container registry. *If you encounter authentication issues, see [Authentication Methods](#authentication-methods).*\n\nTo build to a Docker daemon, use:\n\n```shell\ngradle jibDockerBuild\n```\n\nIf you would like to set up Jib as part of your Gradle build, follow the guide below.\n\n## Configuration\n\nConfigure the plugin by setting the image to push to:\n\n#### Using [Google Container Registry (GCR)](https://cloud.google.com/container-registry/)...\n\n*Make sure you have the [`docker-credential-gcr` command line tool](https://cloud.google.com/container-registry/docs/advanced-authentication#docker_credential_helper). Jib automatically uses `docker-credential-gcr` for obtaining credentials. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `gcr.io/my-gcp-project/my-app`, the configuration would be:\n\n```groovy\njib.to.image = 'gcr.io/my-gcp-project/my-app'\n```\n\n#### Using [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/)...\n\n*Make sure you have the [`docker-credential-ecr-login` command line tool](https://github.com/awslabs/amazon-ecr-credential-helper). Jib automatically uses `docker-credential-ecr-login` for obtaining credentials. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `aws_account_id.dkr.ecr.region.amazonaws.com/my-app`, the configuration would be:\n\n```groovy\njib.to.image = 'aws_account_id.dkr.ecr.region.amazonaws.com/my-app'\n```\n\n#### Using [Docker Hub Registry](https://hub.docker.com/)...\n\n*Make sure you have a [docker-credential-helper](https://github.com/docker/docker-credential-helpers#available-programs) set up. For example, on macOS, the credential helper would be `docker-credential-osxkeychain`. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `my-docker-id/my-app`, the configuration would be:\n\n```groovy\njib.to.image = 'my-docker-id/my-app'\n```\n\n#### Using [JFrog Container Registry (JCR)](https://jfrog.com/container-registry) or [JFrog Artifactory](https://jfrog.com/help/r/jfrog-artifactory-documentation/getting-started-with-artifactory-as-a-docker-registry)...\n\n*Make sure you have a [docker-credential-helper](https://github.com/docker/docker-credential-helpers#available-programs) set up. For example, on macOS, the credential helper would be `docker-credential-osxkeychain`. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `my-company-docker-local.jfrog.io/my-app`, the configuration would be:\n\n```groovy\njib.to.image = 'my-company-docker-local.jfrog.io/my-app'\n```\n\n#### Using [Azure Container Registry (ACR)](https://azure.microsoft.com/en-us/services/container-registry/)...\n\n*Make sure you have a [`ACR Docker Credential Helper`](https://github.com/Azure/acr-docker-credential-helper) installed and set up. For example, on Windows, the credential helper would be `docker-credential-acr-windows`. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `my_acr_name.azurecr.io/my-app`, the configuration would be:\n\n```groovy\njib.to.image = 'my_acr_name.azurecr.io/my-app'\n```\n\n### Build Your Image\n\nBuild your container image with:\n\n```shell\ngradle jib\n```\n\nSubsequent builds are much faster than the initial build.\n\n*Having trouble? Let us know by [submitting an issue](/../../issues/new), contacting us on [Gitter](https://gitter.im/google/jib), or posting to the [Jib users forum](https://groups.google.com/forum/#!forum/jib-users).*\n\n#### Build to Docker daemon\n\nJib can also build your image directly to a Docker daemon. This uses the `docker` command line tool and requires that you have `docker` available on your `PATH`.\n\n```shell\ngradle jibDockerBuild\n```\n\nIf you are using [`minikube`](https://github.com/kubernetes/minikube)'s remote Docker daemon, make sure you [set up the correct environment variables](https://minikube.sigs.k8s.io/docs/handbook/pushing/#1-pushing-directly-to-the-in-cluster-docker-daemon-docker-env) to point to the remote daemon:\n\n```shell\neval $(minikube docker-env)\ngradle jibDockerBuild\n```\n\nAlternatively, you can set environment variables in the Jib configuration. See [`dockerClient`](#dockerclient-closure) for more configuration options.\n\n#### Build an image tarball\n\nYou can build and save your image to disk as a tarball with:\n\n```shell\ngradle jibBuildTar\n```\n\nThis builds and saves your image to `build/jib-image.tar`, which you can load into docker with:\n\n```shell\ndocker load --input build/jib-image.tar\n```\n\n### Run `jib` with each build\n\nYou can also have `jib` run with each build by attaching it to the `build` task:\n\n```groovy\ntasks.build.dependsOn tasks.jib\n```\n\nThen, ```gradle build``` will build and containerize your application.\n\n### Additional Build Artifacts\n\nAs part of an image build, Jib also writes out the _image digest_ and the _image ID_. By default, these are written out to `build/jib-image.digest` and `build/jib-image.id` respectively, but the locations can be configured using the `jib.outputPaths.digest` and `jib.outputPaths.imageId` configuration properties. See [Extended Usage](#outputpaths-closure) for more details.\n\n## Multi Module Projects\n\nSpecial handling of project dependencies is recommended when building complex\nmulti module projects. See [Multi Module Example](https://github.com/GoogleContainerTools/jib/tree/master/examples/multi-module) for detailed information.\n\n## Extended Usage\n\nThe plugin provides the `jib` extension for configuration with the following options for customizing the image build:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`to` | [`to`](#to-closure) | *Required* | Configures the target image to build your application to.\n`from` | [`from`](#from-closure) | See [`from`](#from-closure) | Configures the base image to build your application on top of.\n`container` | [`container`](#container-closure) | See [`container`](#container-closure) | Configures the container that is run from your built image.\n`extraDirectories` | [`extraDirectories`](#extradirectories-closure) | See [`extraDirectories`](#extradirectories-closure) | Configures the directories used to add arbitrary files to the image.\n`outputPaths` | [`outputPaths`](#outputpaths-closure) | See [`outputPaths`](#outputpaths-closure) | Configures the locations of additional build artifacts generated by Jib.\n`dockerClient` | [`dockerClient`](#dockerclient-closure) | See [`dockerClient`](#dockerclient-closure) | Configures Docker for building to/from the Docker daemon.\n`skaffold` | [`skaffold`](#skaffold-integration) | See [`skaffold`](#skaffold-integration) | Configures the internal skaffold tasks. This configuration should only be used when integrating with [`skaffold`](#skaffold-integration). |\n`containerizingMode` | `String` | `exploded` | If set to `packaged`, puts the JAR artifact built by the Gradle Java plugin into the final image. If set to `exploded` (default), containerizes individual `.class` files and resources files.\n`allowInsecureRegistries` | `boolean` | `false` | If set to true, Jib ignores HTTPS certificate errors and may fall back to HTTP as a last resort. Leaving this parameter set to `false` is strongly recommended, since HTTP communication is unencrypted and visible to others on the network, and insecure HTTPS is no better than plain HTTP. [If accessing a registry with a self-signed certificate, adding the certificate to your Java runtime's trusted keys](https://github.com/GoogleContainerTools/jib/tree/master/docs/self_sign_cert.md) may be an alternative to enabling this option.\n`configurationName` | `String` | `runtimeClasspath` | Specify the name of the [Gradle Configuration](https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html) to use.\n\n<a name=\"from-closure\"></a>`from` is a closure with the following properties:\n\nProperty | Type | Default                                                    | Description\n--- | --- |------------------------------------------------------------| ---\n`image` | `String` | `eclipse-temurin:{8,11,17,21,25}-jre` (or `jetty` for WAR) | The image reference for the base image. The source type can be specified using a [special type prefix](#setting-the-base-image).\n`auth` | [`auth`](#auth-closure) | *None*                                                     | Specifies credentials directly (alternative to `credHelper`).\n`credHelper` | `String` | *None*                                                     | Specifies a credential helper that can authenticate pulling the base image. This parameter can either be configured as an absolute path to the credential helper executable or as a credential helper suffix (following `docker-credential-`).\n`platforms` | [`platforms`](#platforms-closure) | See [`platforms`](#platforms-closure)                      | Configures platforms of base images to select from a manifest list.\n\n<a name=\"to-closure\"></a>`to` is a closure with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`image` | `String` | *Required* | The image reference for the target image. This can also be specified via the `--image` command line option. If the tag is not present here `:latest` is implied.\n`auth` | [`auth`](#auth-closure) | *None* | Specifies credentials directly (alternative to `credHelper`).\n`credHelper` | `String` | *None* | Specifies a credential helper that can authenticate pushing the target image. This parameter can either be configured as an absolute path to the credential helper executable or as a credential helper suffix (following `docker-credential-`).\n`tags` | `List<String>` | *None* | Additional tags to push to.\n\n<a name=\"auth-closure\"></a>`auth` is a closure with the following properties (see [Using Specific Credentials](#using-specific-credentials)):\n\nProperty | Type\n--- | ---\n`username` | `String`\n`password` | `String`\n\n<a name=\"platforms-closure\"></a>`platforms` can configure multiple `platform` closures.  Each individual `platform` has the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`architecture` | `String` | `amd64` | The architecture of a base image to select from a manifest list.\n`os` | `String` | `linux` | The OS of a base image to select from a manifest list.\n\nSee [How do I specify a platform in the manifest list (or OCI index) of a base image?](../docs/faq.md#how-do-i-specify-a-platform-in-the-manifest-list-or-oci-index-of-a-base-image) for examples.\n\n<a name=\"container-closure\"></a>`container` is a closure with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`appRoot` | `String` | `/app` | The root directory on the container where the app's contents are placed. Particularly useful for WAR-packaging projects to work with different Servlet engine base images by designating where to put exploded WAR contents; see [WAR usage](#war-projects) as an example.\n`args` | `List<String>` | *None* | Additional program arguments appended to the command to start the container (similar to Docker's [CMD](https://docs.docker.com/engine/reference/builder/#cmd) instruction in relation with [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint)). In the default case where you do not set a custom `entrypoint`, this parameter is effectively the arguments to the main method of your Java application.\n`creationTime` | `String` | `EPOCH` | Sets the container creation time. (Note that this property does not affect the file modification times, which are configured using `jib.container.filesModificationTime`.) The value can be `EPOCH` to set the timestamps to Epoch (default behavior), `USE_CURRENT_TIMESTAMP` to forgo reproducibility and use the real creation time, or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`. The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.\n`entrypoint` | `List<String>` | *None* | The command to start the container with (similar to Docker's [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint) instruction). If set, then `jvmFlags`, `mainClass`, `extraClasspath`, and `expandClasspathDependencies` are ignored. You may also set `jib.container.entrypoint = 'INHERIT'` to indicate that the `entrypoint` and `args` should be inherited from the base image.\\* The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.\n`environment` | `Map<String, String>` | *None* | Key-value pairs for setting environment variables on the container (similar to Docker's [ENV](https://docs.docker.com/engine/reference/builder/#env) instruction).\n`extraClasspath` | `List<String>` | *None* | Additional paths in the container to prepend to the computed Java classpath.\n`expandClasspathDependencies` | `boolean` | `false` | <ul><li>Java 8 *or* Jib < 3.1: When set to true, does not use a wildcard (for example, `/app/lib/*`) for dependency JARs in the default Java runtime classpath but instead enumerates the JARs. Has the effect of preserving the classpath loading order as defined by the Gradle project.</li><li>Java >= 9 *and* Jib >= 3.1: The option has no effect. Jib *always* enumerates the dependency JARs. This is achieved by [creating and using an argument file](#custom-container-entrypoint) for the `--class-path` JVM argument.</li></ul>\n`filesModificationTime` | `String` | `EPOCH_PLUS_SECOND` | Sets the modification time (last modified time) of files in the image put by Jib. (Note that this does not set the image creation time, which can be set using `jib.container.creationTime`.) The value should either be `EPOCH_PLUS_SECOND` to set the timestamps to Epoch + 1 second (default behavior), or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`. The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.\n`format` | `String` | `Docker` | Use `OCI` to build an [OCI container image](https://www.opencontainers.org/).\n`jvmFlags` | `List<String>` | *None* | Additional flags to pass into the JVM when running your application. The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.\n`labels` | `Map<String, String>` | *None* | Key-value pairs for applying image metadata (similar to Docker's [LABEL](https://docs.docker.com/engine/reference/builder/#label) instruction).\n`mainClass` | `String` | *Inferred*\\*\\* | The main class to launch your application from. The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.\n`ports` | `List<String>` | *None* | Ports that the container exposes at runtime (similar to Docker's [EXPOSE](https://docs.docker.com/engine/reference/builder/#expose) instruction).\n`user` | `String` | *None* | The user and group to run the container as. The value can be a username or UID along with an optional groupname or GID. The following are all valid: `user`, `uid`, `user:group`, `uid:gid`, `uid:group`, `user:gid`.\n`volumes` | `List<String>` | *None* | Specifies a list of mount points on the container.\n`workingDirectory` | `String` | *None* | The working directory in the container.\n\n<a name=\"extradirectories-closure\"></a>`extraDirectories` is a closure with the following properties (see [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image)):\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`paths` | [`paths`](#paths-closure) closure, or `Object` | `(project-dir)/src/main/jib` | May be configured as a closure configuring `path` elements, or as source directory values recognized by [`Project.files()`](https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#files-java.lang.Object...-), such as `String`, `File`, `Path`, `List<String\\|File\\|Path>`, etc.\n`permissions` | `Map<String, String>` | *None* | Maps file paths (glob patterns) on container to Unix permissions. (Effective only for files added from extra directories.) If not configured, permissions default to \"755\" for directories and \"644\" for files. See [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) for an example.\n\n<a name=\"paths-closure\"></a>`paths` can configure multiple `path` closures (see [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image)). Each individual `path` has the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`from` | `Object` | `(project-dir)/src/main/jib` | Accepts source directories that are recognized by [`Project.files()`](https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html#files-java.lang.Object...-), such as `String`, `File`, `Path`, `List<String\\|File\\|Path>`, etc.\n`into` | `String` | `/` | The absolute unix path on the container to copy the extra directory contents into.\n`includes` | `List<String>` | *None* | Glob patterns for including files. See [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) for an example.\n`excludes` | `List<String>` | *None* | Glob patterns for excluding files. See [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) for an example.\n\n<a name=\"outputpaths-closure\"></a>`outputPaths` is a closure with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`tar` | `File` | `(project-dir)/build/jib-image.tar` | The path of the tarball generated by `jibBuildTar`. Relative paths are resolved relative to the project root.\n`digest` | `File` | `(project-dir)/build/jib-image.digest` | The path of the image digest written out during the build. Relative paths are resolved relative to the project root.\n`imageId` | `File` | `(project-dir)/build/jib-image.id` | The path of the image ID written out during the build. Relative paths are resolved relative to the project root.\n\n<a name=\"dockerclient-closure\"></a>`dockerClient` is an object used to configure Docker when building to/from the Docker daemon. It has the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`executable` | `File` | `docker` | Sets the path to the Docker executable that is called to load the image into the Docker daemon. **Please note**: Users are responsible for ensuring that the Docker path passed in is valid and has the right permissions to be executed.\n`environment` | `Map<String, String>` | *None* | Sets environment variables used by the Docker executable.\n\n#### System Properties\n\nEach of these parameters is configurable via commandline using system properties. Jib's system properties follow the same naming convention as the configuration parameters, with each level separated by dots (i.e. `-Djib.parameterName[.nestedParameter.[...]]=value`). Some examples are below:\n```shell\ngradle jib \\\n    -Djib.to.image=myregistry/myimage:latest \\\n    -Djib.to.auth.username=$USERNAME \\\n    -Djib.to.auth.password=$PASSWORD\n\ngradle jibDockerBuild \\\n    -Djib.dockerClient.executable=/path/to/docker \\\n    -Djib.container.environment=key1=\"value1\",key2=\"value2\" \\\n    -Djib.container.args=arg1,arg2,arg3\n```\n\nThe following table contains additional system properties that are not available as build configuration parameters:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`jib.httpTimeout` | `int` | `20000` | HTTP connection/read timeout for registry interactions, in milliseconds. Use a value of `0` for an infinite timeout.\n`jib.useOnlyProjectCache` | `boolean` | `false` | If set to true, Jib does not share a cache between different Gradle projects.\n`jib.baseImageCache` | `File` | *Platform-dependent*\\*\\*\\* | Sets the directory to use for caching base image layers. This cache can (and should) be shared between multiple images.\n`jib.applicationCache` | `File` | `[project dir]/build/jib-cache` | Sets the directory to use for caching application layers. This cache can be shared between multiple images.\n`jib.console` | `String` | *None* | If set to `plain`, Jib will print plaintext log messages rather than display a progress bar during the build.\n\n*\\* If you configure `args` while `entrypoint` is set to `'INHERIT'`, the configured `args` value will take precedence over the CMD propagated from the base image.*\n\n*\\*\\* Uses the main class defined in the `jar` task or tries to find a valid main class.*\n\n*\\*\\*\\* The default base image cache is in the following locations on each platform:*\n * *Linux: `[cache root]/google-cloud-tools-java/jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/.cache/` if not set)*\n * *Mac: `[cache root]/Google/Jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/Library/Caches/` if not set)*\n * *Windows: `[cache root]\\Google\\Jib\\Cache`, where `[cache root]` is `%XDG_CACHE_HOME%` (`%LOCALAPPDATA%` if not set)*\n\n### Global Jib Configuration\n\nSome options can be set in the global Jib configuration file. The file is at the following locations on each platform:\n\n* *Linux: `[config root]/google-cloud-tools-java/jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/.config/` if not set)*\n* *Mac: `[config root]/Google/Jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/Library/Preferences/Config/` if not set)*\n* *Windows: `[config root]\\Google\\Jib\\Config\\config.json`, where `[config root]` is `%XDG_CONFIG_HOME%` (`%LOCALAPPDATA%` if not set)*\n\n#### Properties\n\n* `disableUpdateCheck`: when set to true, disables the periodic up-to-date version check.\n* `registryMirrors`: a list of mirror settings for each base image registry. In the following example, if the base image configured in Jib is for a Docker Hub image, then `mirror.gcr.io`, `localhost:5000`, and the Docker Hub (`registry-1.docker.io`) are tried in order until Jib can successfully pull a base image.\n\n```json\n{\n  \"disableUpdateCheck\": false,\n  \"registryMirrors\": [\n    {\n      \"registry\": \"registry-1.docker.io\",\n      \"mirrors\": [\"mirror.gcr.io\", \"localhost:5000\"]\n    },\n    {\n      \"registry\": \"quay.io\",\n      \"mirrors\": [\"private-mirror.test.com\"]\n    }\n  ]\n}\n```\n**Note about `mirror.gcr.io`**: it is _not_ a Docker Hub mirror but a cache. It caches [frequently-accessed public Docker Hub images](https://cloud.google.com/container-registry/docs/pulling-cached-images), and it's often possible that your base image does not exist in `mirror.gcr.io`. In that case, Jib will have to fall back to use Docker Hub.\n\n### Example\n\nIn this configuration, the image:\n* Is built from a base of `openjdk:alpine` (pulled from Docker Hub)\n* Is pushed to `localhost:5000/my-image:built-with-jib`, `localhost:5000/my-image:tag2`, and `localhost:5000/my-image:latest`\n* Runs by calling `java -Dmy.property=example.value -Xms512m -Xdebug -cp app/libs/*:app/resources:app/classes mypackage.MyApp some args`\n* Exposes port 1000 for tcp (default), and ports 2000, 2001, 2002, and 2003 for udp\n* Has two labels (key1:value1 and key2:value2)\n* Is built as OCI format\n\n```groovy\njib {\n  from {\n    image = 'openjdk:alpine'\n  }\n  to {\n    image = 'localhost:5000/my-image/built-with-jib'\n    credHelper = 'osxkeychain'\n    tags = ['tag2', 'latest']\n  }\n  container {\n    jvmFlags = ['-Dmy.property=example.value', '-Xms512m', '-Xdebug']\n    mainClass = 'mypackage.MyApp'\n    args = ['some', 'args']\n    ports = ['1000', '2000-2003/udp']\n    labels = [key1:'value1', key2:'value2']\n    format = 'OCI'\n  }\n}\n```\n\n### Setting the Base Image\n\nThere are three different types of base images that Jib accepts: an image from a container registry, an image stored in the Docker daemon, or an image tarball on the local filesystem. You can specify which you would like to use by prepending the `jib.from.image` configuration with a special prefix, listed below:\n\nPrefix | Example | Type\n--- | --- | ---\n*None* | `openjdk:11-jre` | Pulls the base image from a registry.\n`registry://` | `registry://eclipse-temurin:11-jre` | Pulls the base image from a registry.\n`docker://` | `docker://busybox` | Retrieves the base image from the Docker daemon.\n`tar://` | `tar:///path/to/file.tar` | Uses an image tarball stored at the specified path as the base image. Also accepts relative paths (e.g. `tar://build/jib-image.tar`).\n\n### Adding Arbitrary Files to the Image\n\nYou can add arbitrary, non-classpath files to the image without extra configuration by placing them in a `src/main/jib` directory. This will copy all files within the `jib` folder to the target directory (`/` by default) in the image, maintaining the same structure (e.g. if you have a text file at `src/main/jib/dir/hello.txt`, then your image will contain `/dir/hello.txt` after being built with Jib).\n\nNote that Jib does not follow symbolic links in the container image.  If a symbolic link is present, _it will be removed_ prior to placing the files and directories.\n\nYou can configure different directories by using the `jib.extraDirectories.paths` parameter in your `build.gradle`:\n```groovy\njib {\n  // Copies files from 'src/main/custom-extra-dir' and '/home/user/jib-extras' instead of 'src/main/jib'\n  extraDirectories.paths = ['src/main/custom-extra-dir', '/home/user/jib-extras']\n}\n```\n\nAlternatively, the `jib.extraDirectories` parameter can be used as a closure to set custom extra directories, as well as the extra files' permissions on the container:\n\n```groovy\njib {\n  extraDirectories {\n    paths = 'src/main/custom-extra-dir'  // Copies files from 'src/main/custom-extra-dir'\n    permissions = [\n        '/path/on/container/to/fileA': '755',  // Read/write/execute for owner, read/execute for group/other\n        '/path/to/another/file': '644',  // Read/write for owner, read-only for group/other\n        '/glob/pattern/**/*.sh': 755\n    ]\n  }\n}\n```\n\nUsing `paths` as a closure, you may also specify the target of the copy and include or exclude files:\n\n```groovy\n  extraDirectories {\n    paths {\n      path {\n        // copies the contents of 'src/main/extra-dir' into '/' on the container\n        from = file('src/main/extra-dir')\n      }\n      path {\n        // copies the contents of 'src/main/another/dir' into '/extras' on the container\n        from = file('src/main/another/dir')\n        into = '/extras'\n      }\n      path {\n        // copies a single-file.xml\n        from = 'src/main/resources/xml-files'\n        into = '/dest-in-container'\n        includes = ['single-file.xml']\n      }\n      path {\n        // copies only .txt files except for 'hidden.txt' at the source root\n        from = 'build/some-output'\n        into = '/txt-files'\n        includes = ['*.txt', '**/*.txt']\n        excludes = ['hidden.txt']\n      }\n    }\n  }\n```\n\nYou can also configure `paths` and `permissions` through [lazy configuration in Gradle](https://docs.gradle.org/current/userguide/lazy_configuration.html), using providers in `build.gradle`:\n\n```groovy\nextraDirectories {\n   paths = project.provider { 'src/main/custom-extra-dir' }\n   permissions = project.provider { ['/path/on/container/to/fileA': '755'] }\n}\n```\n\n```groovy\nextraDirectories {\n   paths {\n     path { \n       from = project.provider { 'src/main/custom-extra-dir' }\n       into = project.provider { '/dest-in-container' }\n       includes = project.provider { ['*.txt', '**/*.txt'] }\n       excludes = project.provider { ['hidden.txt'] }\n    }\n  }\n}\n```\n\n### Authentication Methods\n\nPushing/pulling from private registries require authorization credentials.\n\n#### Using Docker configuration files\n\n* Jib looks from credentials from `$XDG_RUNTIME_DIR/containers/auth.json`, `$XDG_CONFIG_HOME/containers/auth.json`, `$HOME/.config/containers/auth.json`, `$DOCKER_CONFIG/config.json`, and `$HOME/.docker/config.json`.\n\nSee [this issue](/../../issues/101) and [`man containers-auth.json`](https://www.mankier.com/5/containers-auth.json) for more information about the files.\n\n#### Using Docker Credential Helpers\n\nDocker credential helpers are CLI tools that handle authentication with various registries.\n\nSome common credential helpers include:\n\n* Google Container Registry: [`docker-credential-gcr`](https://cloud.google.com/container-registry/docs/advanced-authentication#docker_credential_helper)\n* AWS Elastic Container Registry: [`docker-credential-ecr-login`](https://github.com/awslabs/amazon-ecr-credential-helper)\n* Docker Hub Registry: [`docker-credential-*`](https://github.com/docker/docker-credential-helpers)\n* Azure Container Registry: [`docker-credential-acr-*`](https://github.com/Azure/acr-docker-credential-helper)\n\nConfigure credential helpers to use by specifying them as a `credHelper` for their respective image in the `jib` extension.\n\n*Example configuration:*\n```groovy\njib {\n  from {\n    image = 'aws_account_id.dkr.ecr.region.amazonaws.com/my-base-image'\n    credHelper = 'ecr-login'\n  }\n  to {\n    image = 'gcr.io/my-gcp-project/my-app'\n    credHelper = 'gcr'\n  }\n}\n```\n\n#### Using Specific Credentials\n\nYou can specify credentials directly in the extension for the `from` and/or `to` images.\n\n```groovy\njib {\n  from {\n    image = 'aws_account_id.dkr.ecr.region.amazonaws.com/my-base-image'\n    auth {\n      username = USERNAME // Defined in 'gradle.properties'.\n      password = PASSWORD\n    }\n  }\n  to {\n    image = 'gcr.io/my-gcp-project/my-app'\n    auth {\n      username = 'oauth2accesstoken'\n      password = 'gcloud auth print-access-token'.execute().text.trim()\n    }\n  }\n}\n```\n\nThese credentials can be stored in `gradle.properties`, retrieved from a command (like `gcloud auth print-access-token`), or read in from a file.\n\nFor example, you can use a key file for authentication (for GCR, see [Using a JSON key file](https://cloud.google.com/container-registry/docs/advanced-authentication#using_a_json_key_file)):\n\n```groovy\njib {\n  to {\n    image = 'gcr.io/my-gcp-project/my-app'\n    auth {\n      username = '_json_key'\n      password = file('keyfile.json').text\n    }\n  }\n}\n```\n\n\n### Custom Container Entrypoint\n\nIf you don't set `jib.container.entrypoint`, the default container entrypoint to launch your app will be basically `java -cp <runtime classpath> <app main class>`. (The final `java` command can be further configured by setting `jib.container.{jvmFlags|args|extraClasspath|mainClass|expandClasspathDependencies}`.)\n\nSometimes, you'll want to set a custom entrypoint to use a shell to wrap the `java` command. For example, to let `sh` or `bash` [expand environment variables](https://stackoverflow.com/a/59361658/1701388), or to have more sophisticated logic to construct a launch command. (Note, however, that running a command with a shell forks a new child process unless you run it with `exec` like `sh -c \"exec java ...\"`. Whether to run the JVM process as PID 1 or a child process of a PID-1 shell is a [decision you should make carefully](https://github.com/GoogleContainerTools/distroless/issues/550#issuecomment-791610603).) In this scenario, you will want to have a way inside a shell script to reliably know the default runtime classpath and the main class that Jib would use by default. To help this, Jib >= 3.1 creates two JVM argument files under `/app` (the default app root) inside the built image.\n\n- `/app/jib-classpath-file`: runtime classpath that Jib would use for default app launch\n- `/app/jib-main-class-file`: main class\n\nTherefore, *for example*, the following commands will be able to launch your app:\n\n- (Java 9+) `java -cp @/app/jib-classpath-file @/app/jib-main-class-file`\n- (with shell) `java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )`\n\n### Reproducible Build Timestamps\n\nTo ensure that a Jib build is reproducible, Jib sets the image creation time to the Unix epoch (00:00:00, January 1st, 1970 in UTC) and all file modification times to one second past the epoch by default. See the [Jib FAQ](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#why-is-my-image-created-48-years-ago) for more details on reproducible builds.\n\nAnother, more complex way to achieve reproducible builds with stable creation times is to leverage commit timestamps from the project's SCM. For example, the [gradle-git-properties](https://plugins.gradle.org/plugin/com.gorylenko.gradle-git-properties) plugin can be used to inject Git commit information into the current build. These can then be used to configure `jib.container.creationTime`. Since the actual Git information is not yet available at the time the build is configured, it needs to be set through [lazy configuration in Gradle](https://docs.gradle.org/current/userguide/lazy_configuration.html), using a provider in `build.gradle`:\n\n```groovy\njib {\n   container {\n     creationTime = project.provider { project.ext.git['git.commit.time'] }\n   }\n}\n```\n\nThis would build an image with the creation time set to the time of the latest commit from `project.ext.git['git.commit.time']`.\n\n### Jib Extensions\n\nThe Jib build plugins have an extension framework that enables anyone to easily extend Jib's behavior to their needs. We maintain select [first-party](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party) plugins for popular use cases like [fine-grained layer control](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-layer-filter-extension-gradle) and [Quarkus support](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-quarkus-extension-gradle), but anyone can write and publish an extension. Check out the [jib-extensions](https://github.com/GoogleContainerTools/jib-extensions) repository for more information.\n\n\n### WAR Projects\n\nJib also containerizes WAR projects. If the Gradle project uses the [WAR Plugin](https://docs.gradle.org/current/userguide/war_plugin.html), Jib will by default use [`jetty`](https://hub.docker.com/_/jetty) as a base image to deploy the project WAR. No extra configuration is necessary other than using the WAR Plugin to make Jib build WAR images.\n\nNote that Jib will work slightly differently for WAR projects from JAR projects:\n   - `container.mainClass` and `container.jvmFlags` are ignored.\n   - The WAR will be exploded into `/var/lib/jetty/webapps/ROOT`, which is the expected WAR location for the Jetty base image.\n\nTo use a different Servlet engine base image, you can customize `container.appRoot`, `container.entrypoint`, and `container.args`. If you do not set `entrypoint` or `args`, Jib will inherit the `ENTRYPOINT` and `CMD` of the base image, so in many cases, you may not need to configure them. However, you will most likely have to set `container.appRoot` to a proper location depending on the base image. Here is an example of using a Tomcat image:\n\n```gradle\njib {\n  from.image = 'tomcat:8.5-jre8-alpine'\n\n  // For demonstration only: this directory in the base image contains a Tomcat default\n  // app (welcome page), so you may first want to delete this directory in the base image.\n  container.appRoot = '/usr/local/tomcat/webapps/ROOT'\n}\n```\nWhen specifying a [`jetty`](https://hub.docker.com/_/jetty) image yourself with `from.image`, you may run into an issue ([#3204](https://github.com/GoogleContainerTools/jib/issues/3204)) and need to override the entrypoint.\n```gradle\njib {\n  from.image = 'jetty:11.0.2-jre11'\n  container.entrypoint = ['java', '-jar', '/usr/local/jetty/start.jar']\n}\n```\n\n\n### Skaffold Integration\n\nJib is an included builder in [Skaffold](https://github.com/GoogleContainerTools/skaffold). Jib passes build information to skaffold through special internal tasks so that skaffold understands when it should rebuild or synchronize files. For complex builds, the defaults may not be sufficient, so the `jib` extension provides a `skaffold` configuration closure which exposes:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`watch` | [`watch`](#skaffold-watch-closure) | *None* | Additional configuration for file watching\n`sync` | [`sync`](#skaffold-sync-closure) | *None* | Additional configuration for file synchronization\n\n<a name=\"skaffold-watch-closure\"></a>`watch` is a closure with the following properties:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`buildIncludes` | `List<String>` | *None* | Additional build files that skaffold should watch\n`includes` | `List<String>` | *None* | Additional project files or directories that skaffold should watch\n`excludes` | `List<String>` | *None* | Files and directories that skaffold should not watch\n\n<a name=\"skaffold-sync-closure\"></a>`sync` is a closure with the following properties:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`excludes` | `List<String>` | *None* | Files and directories that skaffold should not sync\n\n## <a name=frequently-asked-questions-faq></a>Need Help?\n\nA lot of questions are already answered!\n\n* [Frequently Asked Questions (FAQ)](../docs/faq.md)\n* [Stack Overflow](https://stackoverflow.com/questions/tagged/jib)\n* [GitHub issues](https://github.com/GoogleContainerTools/jib/issues)\n\n_For usage questions, please ask them on Stack Overflow._\n\n## Privacy\n\nSee the [Privacy page](docs/privacy.md).\n\n## Upcoming Features\n\nSee [Milestones](https://github.com/GoogleContainerTools/jib/milestones) for planned features. [Get involved with the community](https://github.com/GoogleContainerTools/jib/tree/master#get-involved-with-the-community) for the latest updates.\n\n## Community\n\nSee the [Jib project README](/../../#community).\n\n## Disclaimer\n\nThis is not an officially supported Google product.\n"
  },
  {
    "path": "jib-gradle-plugin/build.gradle",
    "content": "plugins {\n  id 'java-gradle-plugin'\n  id 'net.researchgate.release'\n  id 'com.gradle.plugin-publish'\n  // for eclipse import modifications\n  id 'eclipse'\n}\n\n/* LOCAL SNAPSHOT DEV */\n// to install for local testing - do not load this plugin when publishing to plugin portal\n// 'maven-publish' and 'com.gradle.plugin-publish' do not interact well with each other\n// https://discuss.gradle.org/t/debug-an-issue-in-publish-plugin-gradle-plugin-not-being-prepended-to-groupid/32720\nif (version.contains('SNAPSHOT')) {\n  apply plugin: 'maven-publish'\n\n  publishing {\n    repositories {\n      mavenLocal()\n    }\n  }\n\n  task install {\n    dependsOn publishToMavenLocal\n  }\n}\n/* LOCAL SNAPSHOT DEV */\n\ndependencies {\n  sourceProject project(':jib-core')\n  sourceProject project(':jib-plugins-common')\n  ensureNoProjectDependencies()\n\n  implementation dependencyStrings.GRADLE_EXTENSION\n\n  testImplementation dependencyStrings.JUNIT\n  testImplementation dependencyStrings.TRUTH\n  testImplementation dependencyStrings.TRUTH8\n  testImplementation dependencyStrings.MOCKITO_CORE\n  testImplementation dependencyStrings.SLF4J_API\n  testImplementation dependencyStrings.SYSTEM_RULES\n\n  testImplementation project(path:':jib-plugins-common', configuration:'tests')\n  integrationTestImplementation project(path:':jib-core', configuration:'integrationTests')\n\n  integrationTestImplementation dependencyStrings.JBCRYPT\n\n  // only for testing a concrete Spring Boot example in a test (not for test infrastructure)\n  testImplementation 'org.springframework.boot:spring-boot-gradle-plugin:2.2.11.RELEASE'\n}\n\n/* RELEASE */\n// Prepare release\nrelease {\n  tagTemplate = 'v$version-gradle'\n  ignoredSnapshotDependencies = [\n    'com.google.cloud.tools:jib-core',\n    'com.google.cloud.tools:jib-plugins-common',\n  ]\n  git {\n    requireBranch = /^gradle-release-v\\d+.*$/  //regex\n  }\n}\n// Gradle Plugin Portal releases\npluginBundle {\n  website = 'https://github.com/GoogleContainerTools/jib/'\n  vcsUrl = 'https://github.com/GoogleContainerTools/jib/'\n  tags = ['google', 'java', 'containers', 'docker', 'kubernetes', 'microservices']\n}\n\ngradlePlugin {\n  testSourceSets sourceSets.integrationTest, sourceSets.test\n  plugins {\n    jibPlugin {\n      id = 'com.google.cloud.tools.jib'\n      displayName = 'Jib'\n      description = 'Containerize your Java application'\n      implementationClass = 'com.google.cloud.tools.jib.gradle.JibPlugin'\n    }\n  }\n}\ntasks.publishPlugins.dependsOn build\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\neclipse.classpath.file.whenMerged {\n  entries.each {\n    if (it.path == 'src/test/resources' || it.path == 'src/integration-test/resources') {\n      it.excludes += 'gradle/projects/'\n    }\n  }\n}\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-gradle-plugin/gradle.properties",
    "content": "version = 3.5.4-SNAPSHOT\n"
  },
  {
    "path": "jib-gradle-plugin/scripts/release.sh",
    "content": "#!/bin/sh\n\nset -o errexit\n\nreadonly PUBLISH_KEY=$(cat \"${KOKORO_KEYSTORE_DIR}/72743_gradle_publish_key\")\nreadonly PUBLISH_SECRET=$(cat \"${KOKORO_KEYSTORE_DIR}/72743_gradle_publish_secret\")\n\nset -o xtrace\n\n# From default hostname, get id of container to exclude\nCONTAINER_ID=$(hostname)\necho \"$CONTAINER_ID\"\n\n# Stops any left-over containers.\ndocker stop $(docker ps --all --quiet | grep -v \"$CONTAINER_ID\") || true\ndocker kill $(docker ps --all --quiet | grep -v \"$CONTAINER_ID\") || true\ncd github/jib\n\necho \"gradle publish\"\n\n# turn of command tracing when dealing with secrets\nset +o xtrace\n\n./gradlew :jib-gradle-plugin:publishPlugins \\\n  -Pgradle.publish.key=\"${PUBLISH_KEY}\" \\\n  -Pgradle.publish.secret=\"${PUBLISH_SECRET}\" \\\n  --info --stacktrace\n"
  },
  {
    "path": "jib-gradle-plugin/scripts/update_gcs_latest.sh",
    "content": "#!/bin/bash -\n# Usage: ./jib-gradle-plugin/scripts/update_gcs_latest.sh <release version>\n\nset -o errexit\n\nEchoRed() {\n\techo \"$(tput setaf 1; tput bold)$1$(tput sgr0)\"\n}\nEchoGreen() {\n\techo \"$(tput setaf 2; tput bold)$1$(tput sgr0)\"\n}\n\nDie() {\n\tEchoRed \"$1\"\n\texit 1\n}\n\n# Usage: CheckVersion <version>\nCheckVersion() {\n    [[ $1 =~ ^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z]+)?$ ]] || Die \"Version: $1 not in ###.###.###[-XXX] format.\"\n}\n\n[ $# -ne 1 ] && Die \"Usage: ./jib-gradle-plugin/scripts/update_gcs_latest.sh <release version>\"\n\nCheckVersion $1\n\nversionString=\"{\\\"latest\\\":\\\"$1\\\"}\"\ndestination=\"gs://jib-versions/jib-gradle\"\n\necho $versionString > jib-gradle\ngsutil cp jib-gradle $destination\ngsutil acl ch -u allUsers:READ $destination\nrm jib-gradle\n\ngcsResult=$(curl https://storage.googleapis.com/jib-versions/jib-gradle)\nif [ \"$gcsResult\" == \"$versionString\" ]\nthen\n  EchoGreen \"Version updated successfully\"\nelse\n  Die \"Version update failed\"\nfi"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/DefaultTargetProjectIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.Command;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport org.gradle.testkit.runner.UnexpectedBuildFailure;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for building \"default-target\" project images. */\npublic class DefaultTargetProjectIntegrationTest {\n\n  @ClassRule\n  public static final TestProject defaultTargetTestProject = new TestProject(\"default-target\");\n\n  /**\n   * Asserts that the test project has the required exposed ports, labels and volumes.\n   *\n   * @param imageReference the image to test\n   * @throws IOException if the {@code docker inspect} command fails to run\n   * @throws InterruptedException if the {@code docker inspect} command is interrupted\n   */\n  private static void assertDockerInspect(String imageReference)\n      throws IOException, InterruptedException {\n    String dockerInspectExposedPorts =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.ExposedPorts}}'\", imageReference)\n            .run();\n    String dockerInspectLabels =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Labels}}'\", imageReference).run();\n\n    MatcherAssert.assertThat(\n        dockerInspectExposedPorts,\n        CoreMatchers.containsString(\n            \"\\\"1000/tcp\\\":{},\\\"2000/udp\\\":{},\\\"2001/udp\\\":{},\\\"2002/udp\\\":{},\\\"2003/udp\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectLabels,\n        CoreMatchers.containsString(\"\\\"key1\\\":\\\"value1\\\",\\\"key2\\\":\\\"value2\\\"\"));\n  }\n\n  @Test\n  public void testBuild_defaultTarget() {\n    // Test error when 'to' is missing\n    try {\n      defaultTargetTestProject.build(\n          \"clean\", \"jib\", \"-Djib.useOnlyProjectCache=true\", \"-x=classes\");\n      Assert.fail();\n    } catch (UnexpectedBuildFailure ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Missing target image parameter, perhaps you should add a 'jib.to.image' \"\n                  + \"configuration parameter to your build.gradle or set the parameter via the \"\n                  + \"commandline (e.g. 'gradle jib --image <your image name>').\"));\n    }\n  }\n\n  @Test\n  public void testDockerDaemon_defaultTarget()\n      throws IOException, InterruptedException, DigestException {\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        JibRunHelper.buildToDockerDaemonAndRun(\n            defaultTargetTestProject,\n            \"default-target-name:default-target-version\",\n            \"build.gradle\"));\n    assertDockerInspect(\"default-target-name:default-target-version\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/EmptyProjectIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.IntegrationTestingConfiguration;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport java.time.Instant;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for building empty project images. */\npublic class EmptyProjectIntegrationTest {\n\n  @ClassRule public static final TestProject emptyTestProject = new TestProject(\"empty\");\n\n  /**\n   * Asserts that the test project has the required exposed ports and labels.\n   *\n   * @param imageReference the image to test\n   * @throws IOException if the {@code docker inspect} command fails to run\n   * @throws InterruptedException if the {@code docker inspect} command is interrupted\n   */\n  private static void assertDockerInspect(String imageReference)\n      throws IOException, InterruptedException {\n    String dockerInspectExposedPorts =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.ExposedPorts}}'\", imageReference)\n            .run();\n    String dockerInspectLabels =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Labels}}'\", imageReference).run();\n    MatcherAssert.assertThat(\n        dockerInspectExposedPorts,\n        CoreMatchers.containsString(\n            \"\\\"1000/tcp\\\":{},\\\"2000/udp\\\":{},\\\"2001/udp\\\":{},\\\"2002/udp\\\":{},\\\"2003/udp\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectLabels,\n        CoreMatchers.containsString(\"\\\"key1\\\":\\\"value1\\\",\\\"key2\\\":\\\"value2\\\"\"));\n  }\n\n  @Test\n  public void testBuild_empty() throws IOException, InterruptedException, DigestException {\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/emptyimage:gradle\"\n            + System.nanoTime();\n    Assert.assertEquals(\"\", JibRunHelper.buildAndRun(emptyTestProject, targetImage));\n    assertDockerInspect(targetImage);\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n  }\n\n  @Test\n  public void testBuild_multipleTags()\n      throws IOException, InterruptedException, InvalidImageReferenceException, DigestException {\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/multitag-image:gradle\"\n            + System.nanoTime();\n    JibRunHelper.buildAndRunAdditionalTag(\n        emptyTestProject, targetImage, \"gradle-2\" + System.nanoTime(), \"\");\n    assertDockerInspect(targetImage);\n  }\n\n  @Test\n  public void testDockerDaemon_empty() throws IOException, InterruptedException, DigestException {\n    String targetImage = \"emptyimage:gradle\" + System.nanoTime();\n    Assert.assertEquals(\n        \"\", JibRunHelper.buildToDockerDaemonAndRun(emptyTestProject, targetImage, \"build.gradle\"));\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n    assertDockerInspect(targetImage);\n  }\n\n  @Test\n  public void testDockerDaemon_userNumeric()\n      throws IOException, InterruptedException, DigestException {\n    String targetImage = \"emptyimage:gradle\" + System.nanoTime();\n    JibRunHelper.buildToDockerDaemon(emptyTestProject, targetImage, \"build.gradle\");\n    Assert.assertEquals(\n        \"12345:54321\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.User}}\", targetImage).run().trim());\n  }\n\n  @Test\n  public void testDockerDaemon_userNames()\n      throws IOException, InterruptedException, DigestException {\n    String targetImage = \"brokenuserimage:gradle\" + System.nanoTime();\n    JibRunHelper.buildToDockerDaemon(emptyTestProject, targetImage, \"build-broken-user.gradle\");\n    Assert.assertEquals(\n        \"myuser:mygroup\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.User}}\", targetImage).run().trim());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/GradleLayerConfigurationIntegrationTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.zip.GZIPInputStream;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\npublic class GradleLayerConfigurationIntegrationTest {\n\n  @ClassRule\n  public static final TestProject multiTestProject = new TestProject(\"all-local-multi-service\");\n\n  @ClassRule public static final TestProject configTestProject = new TestProject(\"simple\");\n\n  @Test\n  public void testGradleLayerConfiguration_configurationName() throws IOException {\n    configTestProject.build(\"jibBuildTar\", \"-b=build-configuration.gradle\");\n    Path jibTar = configTestProject.getProjectRoot().resolve(\"build/jib-image.tar\");\n    List<List<String>> layers = getLayers(jibTar);\n\n    // the expected order is:\n    // no base image layers (scratch)\n    // jvm arg files (0)\n    // classes (1)\n    // resources (2)\n    // dependencies (3)\n    // verify dependencies\n    List<String> dependencies = layers.get(3);\n    assertThat(dependencies).containsExactly(\"app/\", \"app/libs/\", \"app/libs/dependency2\").inOrder();\n  }\n\n  @Test\n  public void testGradleLayerConfiguration_configurationName_prioritizeSystemProperty()\n      throws IOException {\n    configTestProject.build(\n        \"jibBuildTar\",\n        \"--stacktrace\",\n        \"-Djib.configurationName=otherConfiguration\",\n        \"-b=build-configuration.gradle\");\n    Path jibTar = configTestProject.getProjectRoot().resolve(\"build/jib-image.tar\");\n    List<List<String>> layers = getLayers(jibTar);\n\n    // the expected order is:\n    // no base image layers (scratch)\n    // jvm arg files (0)\n    // classes (1)\n    // resources (2)\n    // dependencies (3)\n    // verify dependencies\n    List<String> dependencies = layers.get(3);\n    assertThat(dependencies).containsExactly(\"app/\", \"app/libs/\", \"app/libs/dependency3\").inOrder();\n  }\n\n  @Test\n  public void testGradleLayerConfiguration_multiModule() throws IOException {\n    multiTestProject.build(\":complex-service:jibBuildTar\");\n\n    Path jibTar = multiTestProject.getProjectRoot().resolve(\"complex-service/build/jib-image.tar\");\n\n    List<List<String>> layers = getLayers(jibTar);\n\n    assertThat(layers).hasSize(7);\n\n    // the expected order is:\n    // no base image layers (scratch)\n    // extra-files (0)\n    // jvm arg files (1)\n    // classes (2)\n    // resources (3)\n    // project dependencies (4)\n    // snapshot dependencies (5)\n    // dependencies (6)\n\n    // verify dependencies\n    assertThat(layers.get(6))\n        .containsExactly(\"app/\", \"app/libs/\", \"app/libs/dependency-1.0.0.jar\")\n        .inOrder();\n\n    // verify snapshot dependencies\n    assertThat(layers.get(5))\n        .containsExactly(\"app/\", \"app/libs/\", \"app/libs/dependencyX-1.0.0-SNAPSHOT.jar\")\n        .inOrder();\n\n    // verify project dependencies\n    assertThat(layers.get(4)).containsExactly(\"app/\", \"app/libs/\", \"app/libs/lib.jar\").inOrder();\n\n    // verify resources\n    assertThat(layers.get(3))\n        .containsExactly(\n            \"app/\", \"app/resources/\", \"app/resources/resource1.txt\", \"app/resources/resource2.txt\")\n        .inOrder();\n\n    // verify classes\n    assertThat(layers.get(2))\n        .containsExactly(\n            \"app/\",\n            \"app/classes/\",\n            \"app/classes/com/\",\n            \"app/classes/com/test/\",\n            \"app/classes/com/test/HelloWorld.class\");\n\n    // verify jvm arg files\n    assertThat(layers.get(1))\n        .containsExactly(\"app/\", \"app/jib-classpath-file\", \"app/jib-main-class-file\")\n        .inOrder();\n\n    // verify extra files\n    assertThat(layers.get(0)).containsExactly(\"extra-file\");\n  }\n\n  @Test\n  public void testGradleLayerConfiguration_simpleModule() throws IOException {\n    multiTestProject.build(\":simple-service:jibBuildTar\");\n\n    Path jibTar = multiTestProject.getProjectRoot().resolve(\"simple-service/build/jib-image.tar\");\n\n    List<List<String>> layers = getLayers(jibTar);\n\n    assertThat(layers).hasSize(2);\n\n    // the expected order is:\n    // no base image layers (scratch)\n    // jvm arg files (0)\n    // classes (1)\n\n    // verify classes\n    assertThat(layers.get(1))\n        .containsExactly(\n            \"app/\",\n            \"app/classes/\",\n            \"app/classes/com/\",\n            \"app/classes/com/test/\",\n            \"app/classes/com/test/HelloWorld.class\")\n        .inOrder();\n\n    // verify jvm arg files\n    assertThat(layers.get(0))\n        .containsExactly(\"app/\", \"app/jib-classpath-file\", \"app/jib-main-class-file\")\n        .inOrder();\n  }\n\n  // returns all files in layers (*.tar.gz) in a image tar\n  private List<List<String>> getLayers(Path tar) throws IOException {\n    List<List<String>> layers = new ArrayList<>();\n\n    try (TarArchiveInputStream image = new TarArchiveInputStream(Files.newInputStream(tar))) {\n      TarArchiveEntry entry;\n      while ((entry = image.getNextEntry()) != null) {\n        if (entry.getName().endsWith(\".tar.gz\")) {\n          @SuppressWarnings(\"resource\") // must not close sub-streams\n          TarArchiveInputStream layer = new TarArchiveInputStream(new GZIPInputStream(image));\n          TarArchiveEntry layerEntry;\n          List<String> layerFiles = new ArrayList<>();\n          while ((layerEntry = layer.getNextEntry()) != null) {\n            layerFiles.add(layerEntry.getName());\n          }\n          layers.add(0, layerFiles);\n        }\n      }\n    }\n    return layers;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/JibRunHelper.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestException;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.BuildTask;\nimport org.gradle.testkit.runner.TaskOutcome;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\n\n/** Helper class to run integration tests. */\npublic class JibRunHelper {\n\n  static String buildAndRun(TestProject testProject, String imageReference)\n      throws IOException, InterruptedException, DigestException {\n    return buildAndRun(testProject, imageReference, \"build.gradle\");\n  }\n\n  static String buildAndRun(\n      TestProject testProject,\n      String imageReference,\n      String gradleBuildFile,\n      String... extraRunArguments)\n      throws IOException, InterruptedException, DigestException {\n    BuildResult buildResult =\n        testProject.build(\n            \"clean\",\n            \"jib\",\n            \"-Djib.useOnlyProjectCache=true\",\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + imageReference,\n            \"-Djib.allowInsecureRegistries=\" + imageReference.startsWith(\"localhost\"),\n            \"-b=\" + gradleBuildFile);\n    assertBuildSuccess(buildResult, \"jib\", \"Built and pushed image as \");\n    assertThatExpectedImageDigestAndIdReturned(testProject.getProjectRoot());\n    MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(imageReference));\n\n    return pullAndRunBuiltImage(imageReference, extraRunArguments);\n  }\n\n  static String buildAndRunFromLocalBase(String target, String base)\n      throws IOException, InterruptedException, DigestException {\n    BuildResult buildResult =\n        SingleProjectIntegrationTest.simpleTestProject.build(\n            \"clean\",\n            \"jib\",\n            \"-Djib.useOnlyProjectCache=true\",\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + target,\n            \"-D_BASE_IMAGE=\" + base,\n            \"-Djib.allowInsecureRegistries=\" + target.startsWith(\"localhost\"),\n            \"-b=\" + \"build-local-base.gradle\");\n    assertBuildSuccess(buildResult, \"jib\", \"Built and pushed image as \");\n    assertThatExpectedImageDigestAndIdReturned(\n        SingleProjectIntegrationTest.simpleTestProject.getProjectRoot());\n    MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(target));\n    return pullAndRunBuiltImage(target);\n  }\n\n  static void buildAndRunAdditionalTag(\n      TestProject testProject, String imageReference, String additionalTag, String expectedOutput)\n      throws InvalidImageReferenceException, IOException, InterruptedException, DigestException {\n    BuildResult buildResult =\n        testProject.build(\n            \"clean\",\n            \"jib\",\n            \"-Djib.useOnlyProjectCache=true\",\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + imageReference,\n            \"-Djib.allowInsecureRegistries=\" + imageReference.startsWith(\"localhost\"),\n            \"-D_ADDITIONAL_TAG=\" + additionalTag);\n    assertBuildSuccess(buildResult, \"jib\", \"Built and pushed image as \");\n    assertThatExpectedImageDigestAndIdReturned(testProject.getProjectRoot());\n    MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(imageReference));\n\n    String additionalImageReference =\n        ImageReference.parse(imageReference).withQualifier(additionalTag).toString();\n    MatcherAssert.assertThat(\n        buildResult.getOutput(), CoreMatchers.containsString(additionalImageReference));\n\n    Assert.assertEquals(expectedOutput, pullAndRunBuiltImage(imageReference));\n    Assert.assertEquals(expectedOutput, pullAndRunBuiltImage(additionalImageReference));\n    assertThat(getCreationTime(imageReference)).isEqualTo(Instant.EPOCH);\n    assertThat(getCreationTime(additionalImageReference)).isEqualTo(Instant.EPOCH);\n  }\n\n  static BuildResult buildToDockerDaemon(\n      TestProject testProject, String imageReference, String gradleBuildFile)\n      throws IOException, InterruptedException, DigestException {\n    BuildResult buildResult =\n        testProject.build(\n            \"clean\",\n            \"jibDockerBuild\",\n            \"-Djib.useOnlyProjectCache=true\",\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + imageReference,\n            \"-Djib.allowInsecureRegistries=\" + imageReference.startsWith(\"localhost\"),\n            \"-b=\" + gradleBuildFile);\n    assertBuildSuccess(buildResult, \"jibDockerBuild\", \"Built image to Docker daemon as \");\n    assertThatExpectedImageDigestAndIdReturned(testProject.getProjectRoot());\n    MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(imageReference));\n\n    String history = new Command(\"docker\", \"history\", imageReference).run();\n    MatcherAssert.assertThat(history, CoreMatchers.containsString(\"jib-gradle-plugin\"));\n\n    return buildResult;\n  }\n\n  static String buildToDockerDaemonAndRun(\n      TestProject testProject, String imageReference, String gradleBuildFile)\n      throws IOException, InterruptedException, DigestException {\n    buildToDockerDaemon(testProject, imageReference, gradleBuildFile);\n    return new Command(\"docker\", \"run\", \"--rm\", imageReference).run();\n  }\n\n  /**\n   * Asserts that the test project build output indicates a success.\n   *\n   * @param buildResult the builds results of the project under test\n   * @param taskName the name of the Jib task that was run\n   * @param successMessage a Jib-specific success message to check for\n   */\n  static void assertBuildSuccess(BuildResult buildResult, String taskName, String successMessage) {\n    BuildTask classesTask = buildResult.task(\":classes\");\n    BuildTask jibTask = buildResult.task(\":\" + taskName);\n\n    Assert.assertNotNull(classesTask);\n    Assert.assertEquals(TaskOutcome.SUCCESS, classesTask.getOutcome());\n    Assert.assertNotNull(jibTask);\n    Assert.assertEquals(TaskOutcome.SUCCESS, jibTask.getOutcome());\n    MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(successMessage));\n  }\n\n  static Instant getCreationTime(String imageReference) throws IOException, InterruptedException {\n    String inspect =\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Created}}\", imageReference).run().trim();\n    return Instant.parse(inspect);\n  }\n\n  static void assertThatExpectedImageDigestAndIdReturned(Path projectRoot)\n      throws IOException, DigestException {\n    Path digestPath = projectRoot.resolve(\"build/jib-image.digest\");\n    Assert.assertTrue(Files.exists(digestPath));\n    String digest = new String(Files.readAllBytes(digestPath), StandardCharsets.UTF_8);\n    DescriptorDigest digest1 = DescriptorDigest.fromDigest(digest);\n\n    Path idPath = projectRoot.resolve(\"build/jib-image.id\");\n    Assert.assertTrue(Files.exists(idPath));\n    String id = new String(Files.readAllBytes(idPath), StandardCharsets.UTF_8);\n    DescriptorDigest digest2 = DescriptorDigest.fromDigest(id);\n    Assert.assertNotEquals(digest1, digest2);\n  }\n\n  /**\n   * Pulls a built image and attempts to run it. Also verifies the container configuration and\n   * history of the built image.\n   *\n   * @param imageReference the image reference of the built image\n   * @param extraRunArguments extra arguments passed to {@code docker run}\n   * @return the container output\n   * @throws IOException if an I/O exception occurs\n   * @throws InterruptedException if the process was interrupted\n   */\n  static String pullAndRunBuiltImage(String imageReference, String... extraRunArguments)\n      throws IOException, InterruptedException {\n    new Command(\"docker\", \"pull\", imageReference).run();\n    String history = new Command(\"docker\", \"history\", imageReference).run();\n    MatcherAssert.assertThat(history, CoreMatchers.containsString(\"jib-gradle-plugin\"));\n\n    List<String> command = new ArrayList<>(Arrays.asList(\"docker\", \"run\", \"--rm\"));\n    command.addAll(Arrays.asList(extraRunArguments));\n    command.add(imageReference);\n    return new Command(command).run();\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assume.assumeFalse;\nimport static org.junit.Assume.assumeTrue;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.IntegrationTestingConfiguration;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.registry.LocalRegistry;\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.security.DigestException;\nimport java.time.Instant;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.UnexpectedBuildFailure;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Integration tests for building single project images. */\npublic class SingleProjectIntegrationTest {\n\n  @ClassRule\n  public static final LocalRegistry localRegistry1 =\n      new LocalRegistry(5000, \"testuser\", \"testpassword\");\n\n  @ClassRule\n  public static final LocalRegistry localRegistry2 =\n      new LocalRegistry(6000, \"testuser\", \"testpassword\");\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  private static boolean isJavaRuntimeAtLeast(int version) {\n    Iterable<String> split = Splitter.on(\".\").split(System.getProperty(\"java.version\"));\n    return Integer.valueOf(split.iterator().next()) >= version;\n  }\n\n  private static String getWorkingDirectory(String imageReference)\n      throws IOException, InterruptedException {\n    return new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.WorkingDir}}\", imageReference)\n        .run()\n        .trim();\n  }\n\n  private static String getEntrypoint(String imageReference)\n      throws IOException, InterruptedException {\n    return new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.Entrypoint}}\", imageReference)\n        .run()\n        .trim();\n  }\n\n  private static int getLayerSize(String imageReference) throws IOException, InterruptedException {\n    Command command =\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{join .RootFS.Layers \\\",\\\"}}\", imageReference);\n    String layers = command.run().trim();\n    return Splitter.on(\",\").splitToList(layers).size();\n  }\n\n  /**\n   * Asserts that the test project has the required exposed ports, labels and volumes.\n   *\n   * @param imageReference the image to test\n   * @throws IOException if the {@code docker inspect} command fails to run\n   * @throws InterruptedException if the {@code docker inspect} command is interrupted\n   */\n  private static void assertDockerInspect(String imageReference)\n      throws IOException, InterruptedException {\n    String dockerInspectVolumes =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Volumes}}'\", imageReference).run();\n    String dockerInspectExposedPorts =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.ExposedPorts}}'\", imageReference)\n            .run();\n    String dockerInspectLabels =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Labels}}'\", imageReference).run();\n\n    MatcherAssert.assertThat(\n        dockerInspectVolumes, CoreMatchers.containsString(\"\\\"/var/log\\\":{},\\\"/var/log2\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectExposedPorts,\n        CoreMatchers.containsString(\n            \"\\\"1000/tcp\\\":{},\\\"2000/udp\\\":{},\\\"2001/udp\\\":{},\\\"2002/udp\\\":{},\\\"2003/udp\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectLabels,\n        CoreMatchers.containsString(\"\\\"key1\\\":\\\"value1\\\",\\\"key2\\\":\\\"value2\\\"\"));\n  }\n\n  private static String readDigestFile(Path digestPath) throws IOException, DigestException {\n    assertThat(Files.exists(digestPath)).isTrue();\n    String digest = new String(Files.readAllBytes(digestPath), StandardCharsets.UTF_8);\n    return DescriptorDigest.fromDigest(digest).toString();\n  }\n\n  private static String buildAndRunComplex(\n      String imageReference, String username, String password, LocalRegistry targetRegistry)\n      throws IOException, InterruptedException {\n    Path baseCache = simpleTestProject.getProjectRoot().resolve(\"build/jib-base-cache\");\n    BuildResult buildResult =\n        simpleTestProject.build(\n            \"clean\",\n            \"jib\",\n            \"-Djib.baseImageCache=\" + baseCache,\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + imageReference,\n            \"-D_TARGET_USERNAME=\" + username,\n            \"-D_TARGET_PASSWORD=\" + password,\n            \"-DsendCredentialsOverHttp=true\",\n            \"-b=complex-build.gradle\");\n\n    JibRunHelper.assertBuildSuccess(buildResult, \"jib\", \"Built and pushed image as \");\n    assertThat(buildResult.getOutput()).contains(imageReference);\n\n    targetRegistry.pull(imageReference);\n    assertDockerInspect(imageReference);\n    String history = new Command(\"docker\", \"history\", imageReference).run();\n    assertThat(history).contains(\"jib-gradle-plugin\");\n\n    String output = new Command(\"docker\", \"run\", \"--rm\", imageReference).run();\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. An argument.\",\n            \"1970-01-01T00:00:01Z\",\n            \"rwxr-xr-x\",\n            \"rwxrwxrwx\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\",\n            \"-Xms512m\",\n            \"-Xdebug\",\n            \"envvalue1\",\n            \"envvalue2\");\n    return output;\n  }\n\n  @Before\n  public void setup() throws IOException, InterruptedException {\n    // Pull distroless and push to local registry so we can test 'from' credentials\n    localRegistry1.pullAndPushToLocal(\"gcr.io/distroless/java:latest\", \"distroless/java\");\n  }\n\n  @Test\n  public void testBuild_simple()\n      throws IOException, InterruptedException, DigestException, InvalidImageReferenceException {\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/simpleimage:gradle\"\n            + System.nanoTime();\n\n    // Test empty output error\n    Exception exception =\n        assertThrows(\n            UnexpectedBuildFailure.class,\n            () ->\n                simpleTestProject.build(\n                    \"clean\",\n                    \"jib\",\n                    \"-Djib.useOnlyProjectCache=true\",\n                    \"-Djib.console=plain\",\n                    \"-x=classes\",\n                    \"-D_TARGET_IMAGE=\" + targetImage));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"No classes files were found - did you compile your project?\");\n\n    String output = JibRunHelper.buildAndRun(simpleTestProject, targetImage);\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. An argument.\",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n\n    String digest =\n        readDigestFile(simpleTestProject.getProjectRoot().resolve(\"build/jib-image.digest\"));\n    String imageReferenceWithDigest =\n        ImageReference.parse(targetImage).withQualifier(digest).toString();\n    assertThat(JibRunHelper.pullAndRunBuiltImage(imageReferenceWithDigest)).isEqualTo(output);\n\n    String id = readDigestFile(simpleTestProject.getProjectRoot().resolve(\"build/jib-image.id\"));\n    assertThat(id).isNotEqualTo(digest);\n    assertThat(new Command(\"docker\", \"run\", \"--rm\", id).run()).isEqualTo(output);\n\n    assertDockerInspect(targetImage);\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/home\");\n    assertThat(getEntrypoint(targetImage))\n        .isEqualTo(\n            \"[java -cp /d1:/d2:/app/resources:/app/classes:/app/libs/* com.test.HelloWorld]\");\n    assertThat(getLayerSize(targetImage)).isEqualTo(10);\n  }\n\n  @Test\n  public void testBuild_dockerDaemonBase()\n      throws IOException, InterruptedException, DigestException {\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/simplewithdockerdaemonbase:gradle\"\n            + System.nanoTime();\n    String output =\n        JibRunHelper.buildAndRunFromLocalBase(\n            targetImage, \"docker://gcr.io/distroless/java:latest\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. An argument.\",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n  }\n\n  @Test\n  public void testBuild_tarBase() throws IOException, InterruptedException, DigestException {\n    Path path = temporaryFolder.getRoot().toPath().resolve(\"docker-save-distroless\");\n    new Command(\"docker\", \"save\", \"gcr.io/distroless/java:latest\", \"-o\", path.toString()).run();\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/simplewithtarbase:gradle\"\n            + System.nanoTime();\n    String output = JibRunHelper.buildAndRunFromLocalBase(targetImage, \"tar://\" + path);\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. An argument.\",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n  }\n\n  @Test\n  public void testBuild_failOffline() {\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/simpleimageoffline:gradle\"\n            + System.nanoTime();\n\n    Exception exception =\n        assertThrows(\n            UnexpectedBuildFailure.class,\n            () ->\n                simpleTestProject.build(\n                    \"--offline\",\n                    \"clean\",\n                    \"jib\",\n                    \"-Djib.useOnlyProjectCache=true\",\n                    \"-Djib.console=plain\",\n                    \"-D_TARGET_IMAGE=\" + targetImage));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\"Cannot build to a container registry in offline mode\");\n  }\n\n  @Test\n  public void testDockerDaemon_simpleOnJava17()\n      throws DigestException, IOException, InterruptedException {\n    assumeTrue(isJavaRuntimeAtLeast(17));\n\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-java17.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\"Hello, world. \", \"1970-01-01T00:00:01Z\");\n  }\n\n  @Test\n  public void testDockerDaemon_simpleOnJava11()\n      throws DigestException, IOException, InterruptedException {\n    assumeTrue(isJavaRuntimeAtLeast(11));\n\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-java11.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\"Hello, world. \", \"1970-01-01T00:00:01Z\");\n  }\n\n  @Test\n  public void testDockerDaemon_simpleWithIncompatibleJava11() {\n    assumeTrue(isJavaRuntimeAtLeast(11));\n\n    Exception exception =\n        assertThrows(\n            UnexpectedBuildFailure.class,\n            () ->\n                JibRunHelper.buildToDockerDaemonAndRun(\n                    simpleTestProject, \"willnotbuild\", \"build-java11-incompatible.gradle\"));\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\n            \"Your project is using Java 11 but the base image is for Java 8, perhaps you should \"\n                + \"configure a Java 11-compatible base image using the 'jib.from.image' parameter, \"\n                + \"or set targetCompatibility = 8 or below in your build configuration\");\n  }\n\n  @Test\n  public void testDockerDaemon_simple_multipleExtraDirectories()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-extra-dirs.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. \",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n    assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual\n  }\n\n  @Test\n  public void testDockerDaemon_simple_multipleExtraDirectoriesWithAlternativeConfig()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-extra-dirs2.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. \",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n    assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual\n  }\n\n  @Test\n  public void testDockerDaemon_simple_multipleExtraDirectoriesWithClosure()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-extra-dirs3.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. \",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\",\n            \"baz\",\n            \"1970-01-01T00:00:01Z\");\n    assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual\n  }\n\n  @Test\n  public void testDockerDaemon_simple_extraDirectoriesFiltering()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    JibRunHelper.buildToDockerDaemon(\n        simpleTestProject, targetImage, \"build-extra-dirs-filtering.gradle\");\n    String output =\n        new Command(\"docker\", \"run\", \"--rm\", \"--entrypoint=ls\", targetImage, \"-1R\", \"/extras\")\n            .run();\n\n    //   /extras/cat.txt\n    //   /extras/foo\n    //   /extras/sub/\n    //   /extras/sub/a.json\n    assertThat(output).isEqualTo(\"/extras:\\ncat.txt\\nfoo\\nsub\\n\\n/extras/sub:\\na.json\\n\");\n  }\n\n  @Test\n  public void testBuild_complex()\n      throws IOException, InterruptedException, DigestException, InvalidImageReferenceException {\n    String targetImage = dockerHost + \":6000/compleximage:gradle\" + System.nanoTime();\n    Instant beforeBuild = Instant.now();\n    String output = buildAndRunComplex(targetImage, \"testuser\", \"testpassword\", localRegistry2);\n\n    String digest =\n        readDigestFile(\n            simpleTestProject.getProjectRoot().resolve(\"build/different-jib-image.digest\"));\n    String imageReferenceWithDigest =\n        ImageReference.parse(targetImage).withQualifier(digest).toString();\n    localRegistry2.pull(imageReferenceWithDigest);\n    assertThat(new Command(\"docker\", \"run\", \"--rm\", imageReferenceWithDigest).run())\n        .isEqualTo(output);\n\n    String id =\n        readDigestFile(simpleTestProject.getProjectRoot().resolve(\"different-jib-image.id\"));\n    assertThat(id).isNotEqualTo(digest);\n    assertThat(new Command(\"docker\", \"run\", \"--rm\", id).run()).isEqualTo(output);\n\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isGreaterThan(beforeBuild);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/\");\n  }\n\n  @Test\n  public void testBuild_complex_sameFromAndToRegistry() throws IOException, InterruptedException {\n    String targetImage = dockerHost + \":5000/compleximage:gradle\" + System.nanoTime();\n    Instant beforeBuild = Instant.now();\n    buildAndRunComplex(targetImage, \"testuser\", \"testpassword\", localRegistry1);\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isGreaterThan(beforeBuild);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/\");\n  }\n\n  @Test\n  public void testDockerDaemon_simple() throws IOException, InterruptedException, DigestException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(simpleTestProject, targetImage, \"build.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. An argument.\",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n    assertDockerInspect(targetImage);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/home\");\n  }\n\n  @Test\n  public void testDockerDaemon_jarContainerization()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-jar-containerization.gradle\");\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. \", \"Implementation-Title: helloworld\", \"Implementation-Version: 1\");\n  }\n\n  @Test\n  public void testBuild_skipDownloadingBaseImageLayers() throws IOException, InterruptedException {\n    Path baseLayersCacheDirectory =\n        simpleTestProject.getProjectRoot().resolve(\"build/jib-base-cache/layers\");\n    String targetImage = dockerHost + \":6000/simpleimage:gradle\" + System.nanoTime();\n\n    buildAndRunComplex(targetImage, \"testuser\", \"testpassword\", localRegistry2);\n    // Base image layer tarballs exist.\n    assertThat(Files.exists(baseLayersCacheDirectory)).isTrue();\n    assertThat(baseLayersCacheDirectory.toFile().list().length >= 2).isTrue();\n\n    buildAndRunComplex(targetImage, \"testuser\", \"testpassword\", localRegistry2);\n    // no base layers downloaded after \"gradle clean jib ...\"\n    assertThat(Files.exists(baseLayersCacheDirectory)).isFalse();\n  }\n\n  @Test\n  public void testDockerDaemon_timestampCustom()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    String output =\n        JibRunHelper.buildToDockerDaemonAndRun(\n            simpleTestProject, targetImage, \"build-timestamps-custom.gradle\");\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\"Hello, world. \", \"2011-12-03T01:15:30Z\");\n    assertThat(JibRunHelper.getCreationTime(targetImage))\n        .isEqualTo(Instant.parse(\"2013-11-04T21:29:30Z\"));\n  }\n\n  @Test\n  public void testBuild_dockerClient() throws IOException, InterruptedException, DigestException {\n    assumeFalse(System.getProperty(\"os.name\").startsWith(\"Windows\"));\n    new Command(\n            \"chmod\", \"+x\", simpleTestProject.getProjectRoot().resolve(\"mock-docker.sh\").toString())\n        .run();\n\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n\n    BuildResult buildResult =\n        simpleTestProject.build(\n            \"clean\",\n            \"jibDockerBuild\",\n            \"-Djib.useOnlyProjectCache=true\",\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + targetImage,\n            \"-b=build-dockerclient.gradle\",\n            \"--debug\");\n    JibRunHelper.assertBuildSuccess(\n        buildResult, \"jibDockerBuild\", \"Built image to Docker daemon as \");\n    JibRunHelper.assertThatExpectedImageDigestAndIdReturned(simpleTestProject.getProjectRoot());\n    assertThat(buildResult.getOutput()).contains(targetImage);\n    assertThat(buildResult.getOutput()).contains(\"Docker load called. value1 value2\");\n  }\n\n  @Test\n  public void testBuildTar_simple() throws IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n\n    String outputPath =\n        simpleTestProject\n            .getProjectRoot()\n            .resolve(\"build\")\n            .resolve(\"different-jib-image.tar\")\n            .toString();\n    BuildResult buildResult =\n        simpleTestProject.build(\n            \"clean\",\n            \"jibBuildTar\",\n            \"-Djib.useOnlyProjectCache=true\",\n            \"-Djib.console=plain\",\n            \"-D_TARGET_IMAGE=\" + targetImage);\n\n    JibRunHelper.assertBuildSuccess(buildResult, \"jibBuildTar\", \"Built image tarball at \");\n    assertThat(buildResult.getOutput()).contains(outputPath);\n\n    new Command(\"docker\", \"load\", \"--input\", outputPath).run();\n    String output = new Command(\"docker\", \"run\", \"--rm\", targetImage).run();\n\n    assertThat(ImmutableList.copyOf(output.split(\"\\n\")))\n        .containsExactly(\n            \"Hello, world. An argument.\",\n            \"1970-01-01T00:00:01Z\",\n            \"rw-r--r--\",\n            \"rw-r--r--\",\n            \"foo\",\n            \"cat\",\n            \"1970-01-01T00:00:01Z\",\n            \"1970-01-01T00:00:01Z\");\n    assertDockerInspect(targetImage);\n    assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/home\");\n  }\n\n  @Test\n  public void testCredHelperConfiguration()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:gradle\" + System.nanoTime();\n    assertThat(\n            JibRunHelper.buildToDockerDaemonAndRun(\n                simpleTestProject, targetImage, \"build-cred-helper.gradle\"))\n        .isEqualTo(\"Hello, world. \\n1970-01-01T00:00:01Z\\n\");\n  }\n\n  @Test\n  public void testToDockerDaemon_multiPlatform()\n      throws DigestException, IOException, InterruptedException {\n    String targetImage = \"multiplatform:gradle\" + System.nanoTime();\n    assertThat(\n            JibRunHelper.buildToDockerDaemonAndRun(\n                simpleTestProject, targetImage, \"build-multi-platform.gradle\"))\n        .isEqualTo(\"Hello, world. \\n1970-01-01T00:00:01Z\\n\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SpringBootProjectIntegrationTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.IntegrationTestingConfiguration;\nimport com.google.cloud.tools.jib.api.HttpRequestTester;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.security.DigestException;\nimport javax.annotation.Nullable;\nimport org.junit.After;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for building Spring Boot images. */\npublic class SpringBootProjectIntegrationTest {\n\n  @ClassRule public static final TestProject springBootProject = new TestProject(\"spring-boot\");\n\n  @Nullable private String containerName;\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    if (containerName != null) {\n      new Command(\"docker\", \"stop\", containerName).run();\n    }\n  }\n\n  @Test\n  public void testBuild_packagedMode() throws IOException, InterruptedException, DigestException {\n    buildAndRunWebApp(\"springboot:gradle\", \"build.gradle\");\n\n    String output =\n        new Command(\n                \"docker\",\n                \"exec\",\n                containerName,\n                \"/busybox/wc\",\n                \"-c\",\n                \"/app/classpath/spring-boot-original.jar\")\n            .run();\n\n    Assert.assertEquals(\"1360 /app/classpath/spring-boot-original.jar\\n\", output);\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080\"));\n  }\n\n  private void buildAndRunWebApp(String label, String gradleBuildFile)\n      throws IOException, InterruptedException, DigestException {\n    String nameBase = IntegrationTestingConfiguration.getTestRepositoryLocation() + '/';\n    String targetImage = nameBase + label + System.nanoTime();\n    String output =\n        JibRunHelper.buildAndRun(\n            springBootProject, targetImage, gradleBuildFile, \"--detach\", \"-p8080:8080\");\n    containerName = output.trim();\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/WarProjectIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.IntegrationTestingConfiguration;\nimport com.google.cloud.tools.jib.api.HttpRequestTester;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.security.DigestException;\nimport javax.annotation.Nullable;\nimport org.junit.After;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for building WAR images. */\npublic class WarProjectIntegrationTest {\n\n  @ClassRule public static final TestProject servlet25Project = new TestProject(\"war_servlet25\");\n\n  @Nullable private String containerName;\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    if (containerName != null) {\n      new Command(\"docker\", \"stop\", containerName).run();\n    }\n  }\n\n  @Test\n  public void testBuild_jettyServlet25() throws IOException, InterruptedException, DigestException {\n    verifyBuildAndRun(servlet25Project, \"war_jetty_servlet25:gradle\", \"build.gradle\");\n  }\n\n  @Test\n  public void testBuild_tomcatServlet25()\n      throws IOException, InterruptedException, DigestException {\n    verifyBuildAndRun(servlet25Project, \"war_tomcat_servlet25:gradle\", \"build-tomcat.gradle\");\n  }\n\n  private void verifyBuildAndRun(TestProject project, String label, String gradleBuildFile)\n      throws IOException, InterruptedException, DigestException {\n    String nameBase = IntegrationTestingConfiguration.getTestRepositoryLocation() + '/';\n    String targetImage = nameBase + label + System.nanoTime();\n    String output =\n        JibRunHelper.buildAndRun(project, targetImage, gradleBuildFile, \"--detach\", \"-p8080:8080\");\n    containerName = output.trim();\n\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080/hello\"));\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'eclipse-temurin:8-jdk-focal'\n  container {\n    args = ['An argument.']\n    ports = ['1000/tcp', '2000-2003/udp']\n    labels = [key1:'value1', key2:'value2']\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/gradle.properties",
    "content": "version = default-target-version"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/settings.gradle",
    "content": "rootProject.name = 'default-target-name'\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\nimport dependency.Greeting;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/** Example class that uses a dependency and a resource file. */\npublic class HelloWorld {\n\n  public static void main(String[] args) throws IOException, URISyntaxException {\n    // 'Greeting' comes from the dependency artfiact.\n    String greeting = Greeting.getGreeting();\n\n    // Gets the contents of the resource file 'world'.\n    ClassLoader classLoader = HelloWorld.class.getClassLoader();\n    Path worldFile = Paths.get(classLoader.getResource(\"world\").toURI());\n    String world = new String(Files.readAllBytes(worldFile), StandardCharsets.UTF_8);\n\n    System.out.println(greeting + \", \" + world + \". \" + (args.length > 0 ? args[0] : \"\"));\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/build-broken-user.gradle",
    "content": "plugins {\n  id 'com.google.cloud.tools.jib'\n}\n\n// Applies the java plugin after Jib to make sure it works in this order.\napply plugin: 'java'\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\njib {\n  to {\n    image = System.getProperty(\"_TARGET_IMAGE\")\n    credHelper = 'gcr'\n  }\n  container {\n    user = 'myuser:mygroup'\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/build.gradle",
    "content": "plugins {\n  id 'com.google.cloud.tools.jib'\n}\n\n// Applies the java plugin after Jib to make sure it works in this order.\napply plugin: 'java'\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\njib {\n  from.image = 'eclipse-temurin:8-jdk-focal'\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n    credHelper = 'gcr'\n  }\n  container {\n    user = '12345:54321'\n    ports = ['1000/tcp', '2000-2003/udp']\n    labels = [key1:'value1', key2:'value2']\n  }\n}\n\ndef additionalTag = System.getProperty('_ADDITIONAL_TAG')\nif (additionalTag != null && !additionalTag.isEmpty()) {\n  jib.to.tags = [System.getProperty('_ADDITIONAL_TAG')]\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/src/main/java/com/test/Empty.java",
    "content": "package com.test;\n\npublic class Empty {\n\n  public static void main(String[] args) {}\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/a_packaged/build.gradle",
    "content": "plugins {\n    id 'com.google.cloud.tools.jib'\n    id 'java'\n}\n\ndependencies {\n  implementation project(':b_dependency')\n}\n\njib {\n    to {\n        image = System.getProperty('_TARGET_IMAGE')\n        credHelper = 'gcr'\n    }\n    container {\n        ports = ['1000/tcp', '2000-2003/udp']\n        labels = [key1:'value1', key2:'value2']\n    }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/a_packaged/src/main/java/com/test/Empty.java",
    "content": "package com.test;\n\npublic class Empty {\n\n  public static void main(String[] args) {}\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/b_dependency/build.gradle",
    "content": "apply plugin: 'java-library'\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/b_dependency/src/main/java/com/test/Empty.java",
    "content": "package com.test;\n\npublic class Empty {\n\n  public static void main(String[] args) {}\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/build.gradle",
    "content": "apply plugin: 'base'\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/settings.gradle",
    "content": "include ':a_packaged'\ninclude ':b_dependency'\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-configuration.gradle",
    "content": "plugins {\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n    mavenCentral()\n}\n\njib {\n    from {\n        image = \"scratch\"\n    }\n    configurationName = \"myConfiguration\"\n}\n\n\nconfigurations {\n    register(\"myConfiguration\")\n    register(\"otherConfiguration\")\n}\n\ndependencies {\n    compile files('libs/dependency-1.0.0.jar')\n    runtime 'com.google.guava:guava:30.1-jre'\n    myConfiguration files('libs/dependency2')\n    otherConfiguration files('libs/dependency3')\n}\n\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-cred-helper.gradle",
    "content": "plugins {\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n    from {\n        image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96'\n        credHelper = 'gcr'\n    }\n    to {\n        image = System.getProperty(\"_TARGET_IMAGE\")\n        credHelper {\n            helper = 'gcr'\n            environment = [\n                ENV_VAR: 'A VAR'\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-dockerclient.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  to {\n    image = System.getProperty(\"_TARGET_IMAGE\")\n  }\n  dockerClient {\n    executable = file('mock-docker.sh')\n    environment = [envvar1:'value1', envvar2:'value2']\n  }\n}"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-extra-dirs-filtering.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'busybox'\n  to.image = System.getProperty(\"_TARGET_IMAGE\")\n  extraDirectories {\n    paths {\n      path {\n        from = 'src/main/custom-extra-dir3'\n        into = '/extras'\n        includes = ['**/*a*', '*.txt']\n        excludes = ['**/*.txt']\n      }\n      path {\n        from = 'src/main/custom-extra-dir4'\n        into = '/extras'\n        includes = ['foo']\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-extra-dirs.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96'\n  to.image = System.getProperty(\"_TARGET_IMAGE\")\n  extraDirectories.paths = ['src/main/custom-extra-dir', 'src/main/custom-extra-dir2']\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-extra-dirs2.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96'\n  to.image = System.getProperty(\"_TARGET_IMAGE\")\n  extraDirectories {\n    paths = files('src/main/custom-extra-dir', 'src/main/custom-extra-dir2')\n    permissions = ['/baz':'705']\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-extra-dirs3.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96'\n  to.image = System.getProperty(\"_TARGET_IMAGE\")\n  extraDirectories {\n    paths {\n      path {\n        from = 'src/main/custom-extra-dir'\n        into = '/'\n      }\n      path {\n        from = 'src/main/custom-extra-dir2'\n        into = '/target/on/container'\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-jar-containerization.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njar {\n  manifest {\n    attributes(\n      'Implementation-Title': 'helloworld',\n      'Implementation-Version': '1'\n    )\n  }\n}\n\njib {\n  to.image = System.getProperty(\"_TARGET_IMAGE\")\n  from.image = 'eclipse-temurin:11-jdk-focal'\n  containerizingMode = 'packaged'\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11-incompatible.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 11\ntargetCompatibility = 11\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib.from.image = 'eclipse-temurin:8-jdk-focal'\njib.to.image = System.getProperty(\"_TARGET_IMAGE\")"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 11\ntargetCompatibility = 11\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib.from.image = 'eclipse-temurin:11-jdk-focal'\njib.to.image = System.getProperty(\"_TARGET_IMAGE\")"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java17.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 17\ntargetCompatibility = 17\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib.to.image = System.getProperty(\"_TARGET_IMAGE\")"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-local-base.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from {\n    image = System.getProperty(\"_BASE_IMAGE\")\n  }\n  to {\n    image = System.getProperty(\"_TARGET_IMAGE\")\n    credHelper = 'gcr'\n  }\n  container {\n    creationTime = 'EPOCH'\n    args = ['An argument.']\n    ports = ['1000/tcp', '2000-2003/udp']\n    labels = [key1:'value1', key2:'value2']\n    volumes = ['/var/log', '/var/log2']\n    workingDirectory = '/home'\n    extraClasspath = ['/d1','/d2']\n  }\n  extraDirectories.paths = file('src/main/custom-extra-dir')\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle",
    "content": "plugins {\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation files('libs/dependency-1.0.0.jar')\n}\n\njib {\n    from {\n        image = 'eclipse-temurin:11'\n        platforms {\n            platform {\n                architecture = 'amd64'\n                os = 'linux'\n            }\n            platform {\n                architecture = 'arm64'\n                os = 'linux'\n            }\n        }\n    }\n    to {\n        image = System.getProperty('_TARGET_IMAGE')\n    }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-timestamps-custom.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  compile files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'eclipse-temurin:11-jdk-focal'\n  to.image = System.getProperty(\"_TARGET_IMAGE\")\n  container {\n    filesModificationTime = '2011-12-03T10:15:30+09:00'\n    creationTime = '2013-11-05T06:29:30+09:00'\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  from.image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96'\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n    credHelper = 'gcr'\n  }\n  container {\n    creationTime = 'EPOCH'\n    args = ['An argument.']\n    ports = ['1000/tcp', '2000-2003/udp']\n    labels = [key1:'value1', key2:'value2']\n    volumes = ['/var/log', '/var/log2']\n    workingDirectory = '/home'\n    extraClasspath = ['/d1','/d2']\n  }\n  outputPaths {\n    tar = file(\"$buildDir/different-jib-image.tar\")\n  }\n  extraDirectories.paths = file('src/main/custom-extra-dir')\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/complex-build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation files('libs/dependency-1.0.0.jar')\n}\n\n\njib {\n  from {\n    def dockerHost = System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\"\n    image = dockerHost + ':5000/distroless/java'\n    auth {\n      username = 'testuser'\n      password = 'testpassword'\n    }\n  }\n  to {\n    image = System.getProperty(\"_TARGET_IMAGE\")\n    auth {\n      username = System.getProperty(\"_TARGET_USERNAME\")\n      password = System.getProperty(\"_TARGET_PASSWORD\")\n    }\n  }\n  container {\n    creationTime = 'USE_CURRENT_TIMESTAMP'\n    args = ['An argument.']\n    mainClass = 'com.test.HelloWorld'\n    jvmFlags = ['-Xms512m', '-Xdebug']\n    environment = [env1:'envvalue1', env2:'envvalue2']\n    ports = ['1000/tcp', '2000-2003/udp']\n    labels = [key1:'value1', key2:'value2']\n    volumes = ['/var/log', '/var/log2']\n  }\n  extraDirectories {\n    paths = file('src/main/custom-extra-dir')\n    permissions = ['/foo':'755', '/bar/cat':'777']\n  }\n  outputPaths {\n    digest = file(\"$buildDir/different-jib-image.digest\")\n    imageId = file(\"different-jib-image.id\")\n  }\n  allowInsecureRegistries = true\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/libs/dependency2",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/libs/dependency3",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/mock-docker.sh",
    "content": "#!/bin/bash\n\nif [[ \"$1\" == \"info\" ]]; then\n  # Output the JSON string\n  echo \"{\\\"OSType\\\":\\\"linux\\\",\\\"Architecture\\\":\\\"x86_64\\\"}\"\n  exit 0\nfi\n\n# Read stdin to avoid broken pipe\ncat > /dev/null\n\necho \"Docker load called. $envvar1 $envvar2\""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir/bar/cat",
    "content": "cat"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir/foo",
    "content": "foo"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir2/baz",
    "content": "baz"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir3/cat.json",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir3/cat.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir3/sub/a.json",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir3/sub/a.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir4/bar",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/custom-extra-dir4/foo",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\nimport dependency.Greeting;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.management.ManagementFactory;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermissions;\n\n/** Example class that uses a dependency and a resource file. */\npublic class HelloWorld {\n\n  public static void main(String[] args) throws IOException, URISyntaxException {\n    // 'Greeting' comes from the dependency artfiact.\n    String greeting = Greeting.getGreeting();\n\n    // Gets the contents of the resource file 'world'.\n    try (BufferedReader reader =\n        new BufferedReader(\n            new InputStreamReader(\n                HelloWorld.class.getResourceAsStream(\"/world\"), StandardCharsets.UTF_8))) {\n      String world = reader.readLine();\n      System.out.println(greeting + \", \" + world + \". \" + (args.length > 0 ? args[0] : \"\"));\n      Path worldFilePath = Paths.get(\"/app/resources/world\");\n      if (worldFilePath.toFile().exists()) {\n        System.out.println(Files.getLastModifiedTime(worldFilePath).toString());\n      }\n\n      // Prints the contents of the extra files.\n      if (Files.exists(Paths.get(\"/foo\"))) {\n        System.out.println(\n            PosixFilePermissions.toString(Files.getPosixFilePermissions(Paths.get(\"/foo\"))));\n        System.out.println(\n            PosixFilePermissions.toString(Files.getPosixFilePermissions(Paths.get(\"/bar/cat\"))));\n        System.out.println(\n            new String(Files.readAllBytes(Paths.get(\"/foo\")), StandardCharsets.UTF_8));\n        System.out.println(\n            new String(Files.readAllBytes(Paths.get(\"/bar/cat\")), StandardCharsets.UTF_8));\n        System.out.println(Files.getLastModifiedTime(Paths.get(\"/foo\")).toString());\n        System.out.println(Files.getLastModifiedTime(Paths.get(\"/bar/cat\")).toString());\n      }\n      // Prints the contents of the files in the second extra directory.\n      if (Files.exists(Paths.get(\"/target/on/container/baz\"))) {\n        System.out.println(\n            new String(\n                Files.readAllBytes(Paths.get(\"/target/on/container/baz\")), StandardCharsets.UTF_8));\n        System.out.println(\n            Files.getLastModifiedTime(Paths.get(\"/target/on/container/baz\")).toString());\n      }\n\n      // Prints jvm flags\n      for (String jvmFlag : ManagementFactory.getRuntimeMXBean().getInputArguments()) {\n        System.out.println(jvmFlag);\n      }\n\n      if (System.getenv(\"env1\") != null) {\n        System.out.println(System.getenv(\"env1\"));\n      }\n      if (System.getenv(\"env2\") != null) {\n        System.out.println(System.getenv(\"env2\"));\n      }\n\n      Package pack = HelloWorld.class.getPackage();\n      if (pack.getImplementationTitle() != null) {\n        System.out.println(\"Implementation-Title: \" + pack.getImplementationTitle());\n        System.out.println(\"Implementation-Version: \" + pack.getImplementationVersion());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/build.gradle",
    "content": "plugins {\n    id 'org.springframework.boot' version '2.1.6.RELEASE'\n    id 'io.spring.dependency-management' version '1.0.6.RELEASE'\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n}\n\njib {\n    from.image = 'gcr.io/distroless/java:debug'\n    to.image = System.getProperty('_TARGET_IMAGE')\n    to.credHelper = 'gcr'\n    containerizingMode='packaged'\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\nrootProject.name = 'spring-boot'"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/src/main/java/hello/Application.java",
    "content": "package hello;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/src/main/java/hello/HelloController.java",
    "content": "package hello;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HelloController {\n\n  @RequestMapping(\"/\")\n  public String index() {\n    return \"Hello world\";\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build-tomcat.gradle",
    "content": "plugins {\n  id 'java'\n  id 'war'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\nconfigurations {\n  moreLibs\n}\n\ndependencies {\n  providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0'\n  moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR\n}\n\nwar {\n  from ('src/extra_static')\n  from ('src/extra_js', { into 'js' })\n  classpath configurations.moreLibs\n}\n\njib {\n  from.image = 'tomcat:10-jre8-temurin-focal'\n  to {\n    image = System.getProperty(\"_TARGET_IMAGE\")\n    credHelper = 'gcr'\n  }\n  container.appRoot = '/usr/local/tomcat/webapps/ROOT'\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'war'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\nconfigurations {\n  moreLibs\n}\n\ndependencies {\n  providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0'\n  moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR\n}\n\nwar {\n  from ('src/extra_static')\n  from ('src/extra_js', { into 'js' })\n  classpath configurations.moreLibs\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n    credHelper = 'gcr'\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/extra_js/bogus.js",
    "content": "// nothing inside\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/extra_static/bogus.html",
    "content": "nothing inside\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport jakarta.servlet.http.HttpServlet;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class HelloWorld extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {\n    try {\n      URL worldFile = getServletContext().getResource(\"/WEB-INF/classes/world\");\n      Path path = Paths.get(worldFile.toURI());\n      String world = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);\n\n      response.setContentType(\"text/plain\");\n      response.setCharacterEncoding(\"UTF-8\");\n\n      response.getWriter().print(\"Hello \" + world);\n\n    } catch (URISyntaxException e) {\n      throw new IOException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/webapp/META-INF/MANIFEST.MF",
    "content": "Manifest-Version: 1.0\nClass-Path: \n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Using the old Servlet API 2.5 for demonstration purposes. -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         version=\"2.5\">\n  <servlet>\n    <servlet-name>HelloWorld</servlet-name>\n    <servlet-class>example.HelloWorld</servlet-class>\n  </servlet>\n  <servlet-mapping>\n    <servlet-name>HelloWorld</servlet-name>\n    <url-pattern>/hello</url-pattern>\n  </servlet-mapping>\n</web-app>\n"
  },
  {
    "path": "jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/webapp/index.html",
    "content": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=UTF-8\" />\n    <title>Hello World</title>\n  </head>\n\n  <body>\n    <h1>Hello World!</h1>\n\n    <table>\n      <tr>\n        <td colspan=\"2\" style=\"font-weight:bold;\">Available Servlets:</td>\n      </tr>\n      <tr>\n        <td><a href='/hello'>The HelloWorld servlet</a></td>\n      </tr>\n    </table>\n  </body>\n</html>\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/AuthParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport javax.annotation.Nullable;\nimport javax.inject.Inject;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\nimport org.gradle.api.tasks.Optional;\n\n/**\n * A bean that configures authorization credentials to be used for a registry. This is configurable\n * with Groovy closures and can be validated when used as a task input.\n */\npublic class AuthParameters implements AuthProperty {\n\n  private Property<String> username;\n  private Property<String> password;\n  private final String source;\n\n  @Inject\n  public AuthParameters(ObjectFactory objectFactory, String source) {\n    username = objectFactory.property(String.class);\n    password = objectFactory.property(String.class);\n    this.source = source;\n  }\n\n  @Input\n  @Optional\n  @Override\n  @Nullable\n  public String getUsername() {\n    return username.getOrNull();\n  }\n\n  public void setUsername(String username) {\n    this.username.set(username);\n  }\n\n  public void setUsername(Provider<String> username) {\n    this.username.set(username);\n  }\n\n  @Input\n  @Optional\n  @Override\n  @Nullable\n  public String getPassword() {\n    return password.getOrNull();\n  }\n\n  public void setPassword(String password) {\n    this.password.set(password);\n  }\n\n  public void setPassword(Provider<String> password) {\n    this.password.set(password);\n  }\n\n  @Internal\n  @Override\n  public String getAuthDescriptor() {\n    return source;\n  }\n\n  @Internal\n  @Override\n  public String getUsernameDescriptor() {\n    return getAuthDescriptor() + \".username\";\n  }\n\n  @Internal\n  @Override\n  public String getPasswordDescriptor() {\n    return getAuthDescriptor() + \".password\";\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BaseImageParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Nested;\nimport org.gradle.api.tasks.Optional;\n\n/** Object in {@link JibExtension} that configures the base image. */\npublic class BaseImageParameters {\n\n  private final AuthParameters auth;\n  private final Property<String> image;\n  private final CredHelperParameters credHelper;\n  private final PlatformParametersSpec platformParametersSpec;\n  private final ListProperty<PlatformParameters> platforms;\n\n  @Inject\n  public BaseImageParameters(ObjectFactory objectFactory) {\n    auth = objectFactory.newInstance(AuthParameters.class, \"from.auth\");\n    platforms = objectFactory.listProperty(PlatformParameters.class);\n    image = objectFactory.property(String.class);\n    platformParametersSpec = objectFactory.newInstance(PlatformParametersSpec.class, platforms);\n    credHelper =\n        objectFactory.newInstance(CredHelperParameters.class, PropertyNames.FROM_CRED_HELPER);\n\n    PlatformParameters amd64Linux = new PlatformParameters();\n    amd64Linux.setArchitecture(\"amd64\");\n    amd64Linux.setOs(\"linux\");\n    platforms.add(amd64Linux);\n  }\n\n  @Nested\n  @Optional\n  public ListProperty<PlatformParameters> getPlatforms() {\n    String property = System.getProperty(PropertyNames.FROM_PLATFORMS);\n    if (property != null) {\n      List<PlatformParameters> parsed =\n          ConfigurationPropertyValidator.parseListProperty(property).stream()\n              .map(PlatformParameters::of)\n              .collect(Collectors.toList());\n      if (!parsed.equals(platforms.get())) {\n        platforms.set(parsed);\n      }\n    }\n    return platforms;\n  }\n\n  public void platforms(Action<? super PlatformParametersSpec> action) {\n    platforms.empty();\n    action.execute(platformParametersSpec);\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public String getImage() {\n    if (System.getProperty(PropertyNames.FROM_IMAGE) != null) {\n      return System.getProperty(PropertyNames.FROM_IMAGE);\n    }\n    return image.getOrNull();\n  }\n\n  public void setImage(String image) {\n    this.image.set(image);\n  }\n\n  public void setImage(Provider<String> image) {\n    this.image.set(image);\n  }\n\n  @Nested\n  @Optional\n  public CredHelperParameters getCredHelper() {\n    return credHelper;\n  }\n\n  public void setCredHelper(String helper) {\n    this.credHelper.setHelper(helper);\n  }\n\n  public void credHelper(Action<? super CredHelperParameters> action) {\n    action.execute(credHelper);\n  }\n\n  @Nested\n  @Optional\n  public AuthParameters getAuth() {\n    // System properties are handled in ConfigurationPropertyValidator\n    return auth;\n  }\n\n  public void auth(Action<? super AuthParameters> action) {\n    action.execute(auth);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException;\nimport com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException;\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\nimport com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidAppRootException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidCreationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidPlatformException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException;\nimport com.google.cloud.tools.jib.plugins.common.MainClassInferenceException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.InvalidGlobalConfigException;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.base.Preconditions;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport javax.annotation.Nullable;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.tasks.Nested;\nimport org.gradle.api.tasks.TaskAction;\nimport org.gradle.api.tasks.options.Option;\n\n/** Builds a container image and exports to the default Docker daemon. */\npublic class BuildDockerTask extends DefaultTask implements JibTask {\n\n  private static final String HELPFUL_SUGGESTIONS_PREFIX = \"Build to Docker daemon failed\";\n\n  @Nullable private JibExtension jibExtension;\n\n  /**\n   * This will call the property {@code \"jib\"} so that it is the same name as the extension. This\n   * way, the user would see error messages for missing configuration with the prefix {@code jib.}.\n   *\n   * @return the {@link JibExtension}.\n   */\n  @Nested\n  @Nullable\n  public JibExtension getJib() {\n    return jibExtension;\n  }\n\n  /**\n   * The target image can be overridden with the {@code --image} command line option.\n   *\n   * @param targetImage the name of the 'to' image.\n   */\n  @Option(option = \"image\", description = \"The image reference for the target image\")\n  public void setTargetImage(String targetImage) {\n    Preconditions.checkNotNull(jibExtension).getTo().setImage(targetImage);\n  }\n\n  /**\n   * Task Action, builds an image to docker daemon.\n   *\n   * @throws IOException if an error occurs creating the jib runner\n   * @throws BuildStepsExecutionException if an error occurs while executing build steps\n   * @throws CacheDirectoryCreationException if a new cache directory could not be created\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidGlobalConfigException if the global config file is invalid\n   */\n  @TaskAction\n  public void buildDocker()\n      throws IOException, BuildStepsExecutionException, CacheDirectoryCreationException,\n          MainClassInferenceException, InvalidGlobalConfigException {\n    Preconditions.checkNotNull(jibExtension);\n\n    // Check deprecated parameters\n    Path dockerExecutable = jibExtension.getDockerClient().getExecutablePath();\n    boolean isDockerInstalled =\n        dockerExecutable == null\n            ? CliDockerClient.isDefaultDockerInstalled()\n            : CliDockerClient.isDockerInstalled(dockerExecutable);\n    if (!isDockerInstalled) {\n      throw new GradleException(\n          HelpfulSuggestions.forDockerNotInstalled(HELPFUL_SUGGESTIONS_PREFIX));\n    }\n\n    TaskCommon.disableHttpLogging();\n    TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n\n    GradleProjectProperties projectProperties =\n        GradleProjectProperties.getForProject(\n            getProject(),\n            getLogger(),\n            tempDirectoryProvider,\n            jibExtension.getConfigurationName().get());\n    GlobalConfig globalConfig = GlobalConfig.readConfig();\n    Future<Optional<String>> updateCheckFuture =\n        TaskCommon.newUpdateChecker(projectProperties, globalConfig, getLogger());\n    try {\n\n      PluginConfigurationProcessor.createJibBuildRunnerForDockerDaemonImage(\n              new GradleRawConfiguration(jibExtension),\n              ignored -> java.util.Optional.empty(),\n              projectProperties,\n              globalConfig,\n              new GradleHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))\n          .runBuild();\n\n    } catch (InvalidAppRootException ex) {\n      throw new GradleException(\n          \"container.appRoot is not an absolute Unix-style path: \" + ex.getInvalidPathValue(), ex);\n\n    } catch (InvalidContainerizingModeException ex) {\n      throw new GradleException(\n          \"invalid value for containerizingMode: \" + ex.getInvalidContainerizingMode(), ex);\n\n    } catch (InvalidWorkingDirectoryException ex) {\n      throw new GradleException(\n          \"container.workingDirectory is not an absolute Unix-style path: \"\n              + ex.getInvalidPathValue(),\n          ex);\n\n    } catch (InvalidPlatformException ex) {\n      throw new GradleException(\n          \"from.platforms contains a platform configuration that is missing required values or has invalid values: \"\n              + ex.getMessage()\n              + \": \"\n              + ex.getInvalidPlatform(),\n          ex);\n\n    } catch (InvalidContainerVolumeException ex) {\n      throw new GradleException(\n          \"container.volumes is not an absolute Unix-style path: \" + ex.getInvalidVolume(), ex);\n\n    } catch (InvalidFilesModificationTimeException ex) {\n      throw new GradleException(\n          \"container.filesModificationTime should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or special keyword \\\"EPOCH_PLUS_SECOND\\\": \"\n              + ex.getInvalidFilesModificationTime(),\n          ex);\n\n    } catch (InvalidCreationTimeException ex) {\n      throw new GradleException(\n          \"container.creationTime should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or a special keyword (\\\"EPOCH\\\", \"\n              + \"\\\"USE_CURRENT_TIMESTAMP\\\"): \"\n              + ex.getInvalidCreationTime(),\n          ex);\n\n    } catch (JibPluginExtensionException ex) {\n      String extensionName = ex.getExtensionClass().getName();\n      throw new GradleException(\n          \"error running extension '\" + extensionName + \"': \" + ex.getMessage(), ex);\n\n    } catch (IncompatibleBaseImageJavaVersionException ex) {\n      throw new GradleException(\n          HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForGradle(\n              ex.getBaseImageMajorJavaVersion(), ex.getProjectMajorJavaVersion()),\n          ex);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new GradleException(\n          HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex);\n\n    } catch (ExtraDirectoryNotFoundException ex) {\n      throw new GradleException(\n          \"extraDirectories.paths contain \\\"from\\\" directory that doesn't exist locally: \"\n              + ex.getPath(),\n          ex);\n    } finally {\n      tempDirectoryProvider.close();\n      TaskCommon.finishUpdateChecker(projectProperties, updateCheckFuture);\n      projectProperties.waitForLoggingThread();\n    }\n  }\n\n  @Override\n  public BuildDockerTask setJibExtension(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n    return this;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException;\nimport com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException;\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\nimport com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidAppRootException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidCreationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidPlatformException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException;\nimport com.google.cloud.tools.jib.plugins.common.MainClassInferenceException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.InvalidGlobalConfigException;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport javax.annotation.Nullable;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.tasks.Nested;\nimport org.gradle.api.tasks.TaskAction;\nimport org.gradle.api.tasks.options.Option;\n\n/** Builds a container image to registry. */\npublic class BuildImageTask extends DefaultTask implements JibTask {\n\n  private static final String HELPFUL_SUGGESTIONS_PREFIX = \"Build image failed\";\n\n  @Nullable private JibExtension jibExtension;\n\n  /**\n   * This will call the property {@code \"jib\"} so that it is the same name as the extension. This\n   * way, the user would see error messages for missing configuration with the prefix {@code jib.}.\n   *\n   * @return the {@link JibExtension}.\n   */\n  @Nested\n  @Nullable\n  public JibExtension getJib() {\n    return jibExtension;\n  }\n\n  /**\n   * The target image can be overridden with the {@code --image} command line option.\n   *\n   * @param targetImage the name of the 'to' image.\n   */\n  @Option(option = \"image\", description = \"The image reference for the target image\")\n  public void setTargetImage(String targetImage) {\n    Preconditions.checkNotNull(jibExtension).getTo().setImage(targetImage);\n  }\n\n  /**\n   * Task Action, builds an image to remote registry.\n   *\n   * @throws IOException if an error occurs creating the jib runner\n   * @throws BuildStepsExecutionException if an error occurs while executing build steps\n   * @throws CacheDirectoryCreationException if a new cache directory could not be created\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidGlobalConfigException if the global config file is invalid\n   */\n  @TaskAction\n  public void buildImage()\n      throws IOException, BuildStepsExecutionException, CacheDirectoryCreationException,\n          MainClassInferenceException, InvalidGlobalConfigException {\n    // Asserts required @Input parameters are not null.\n    Preconditions.checkNotNull(jibExtension);\n    TaskCommon.disableHttpLogging();\n    TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n\n    GradleProjectProperties projectProperties =\n        GradleProjectProperties.getForProject(\n            getProject(),\n            getLogger(),\n            tempDirectoryProvider,\n            jibExtension.getConfigurationName().get());\n    GlobalConfig globalConfig = GlobalConfig.readConfig();\n    Future<Optional<String>> updateCheckFuture =\n        TaskCommon.newUpdateChecker(projectProperties, globalConfig, getLogger());\n    try {\n      if (Strings.isNullOrEmpty(jibExtension.getTo().getImage())) {\n        throw new GradleException(\n            HelpfulSuggestions.forToNotConfigured(\n                \"Missing target image parameter\",\n                \"'jib.to.image'\",\n                \"build.gradle\",\n                \"gradle jib --image <your image name>\"));\n      }\n\n      PluginConfigurationProcessor.createJibBuildRunnerForRegistryImage(\n              new GradleRawConfiguration(jibExtension),\n              ignored -> Optional.empty(),\n              projectProperties,\n              globalConfig,\n              new GradleHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))\n          .runBuild();\n\n    } catch (InvalidAppRootException ex) {\n      throw new GradleException(\n          \"container.appRoot is not an absolute Unix-style path: \" + ex.getInvalidPathValue(), ex);\n\n    } catch (InvalidContainerizingModeException ex) {\n      throw new GradleException(\n          \"invalid value for containerizingMode: \" + ex.getInvalidContainerizingMode(), ex);\n\n    } catch (InvalidWorkingDirectoryException ex) {\n      throw new GradleException(\n          \"container.workingDirectory is not an absolute Unix-style path: \"\n              + ex.getInvalidPathValue(),\n          ex);\n    } catch (InvalidPlatformException ex) {\n      throw new GradleException(\n          \"from.platforms contains a platform configuration that is missing required values or has invalid values: \"\n              + ex.getMessage()\n              + \": \"\n              + ex.getInvalidPlatform(),\n          ex);\n\n    } catch (InvalidContainerVolumeException ex) {\n      throw new GradleException(\n          \"container.volumes is not an absolute Unix-style path: \" + ex.getInvalidVolume(), ex);\n\n    } catch (InvalidFilesModificationTimeException ex) {\n      throw new GradleException(\n          \"container.filesModificationTime should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or special keyword \\\"EPOCH_PLUS_SECOND\\\": \"\n              + ex.getInvalidFilesModificationTime(),\n          ex);\n\n    } catch (InvalidCreationTimeException ex) {\n      throw new GradleException(\n          \"container.creationTime should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or a special keyword (\\\"EPOCH\\\", \"\n              + \"\\\"USE_CURRENT_TIMESTAMP\\\"): \"\n              + ex.getInvalidCreationTime(),\n          ex);\n\n    } catch (JibPluginExtensionException ex) {\n      String extensionName = ex.getExtensionClass().getName();\n      throw new GradleException(\n          \"error running extension '\" + extensionName + \"': \" + ex.getMessage(), ex);\n\n    } catch (IncompatibleBaseImageJavaVersionException ex) {\n      throw new GradleException(\n          HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForGradle(\n              ex.getBaseImageMajorJavaVersion(), ex.getProjectMajorJavaVersion()),\n          ex);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new GradleException(\n          HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex);\n\n    } catch (ExtraDirectoryNotFoundException ex) {\n      throw new GradleException(\n          \"extraDirectories.paths contain \\\"from\\\" directory that doesn't exist locally: \"\n              + ex.getPath(),\n          ex);\n    } finally {\n      tempDirectoryProvider.close();\n      TaskCommon.finishUpdateChecker(projectProperties, updateCheckFuture);\n      projectProperties.waitForLoggingThread();\n    }\n  }\n\n  @Override\n  public BuildImageTask setJibExtension(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n    return this;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException;\nimport com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException;\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\nimport com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidAppRootException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidCreationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidPlatformException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException;\nimport com.google.cloud.tools.jib.plugins.common.MainClassInferenceException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.InvalidGlobalConfigException;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.base.Preconditions;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.file.FileCollection;\nimport org.gradle.api.tasks.InputFiles;\nimport org.gradle.api.tasks.Nested;\nimport org.gradle.api.tasks.OutputFile;\nimport org.gradle.api.tasks.TaskAction;\nimport org.gradle.api.tasks.options.Option;\n\n/** Builds a container image to a tarball. */\npublic class BuildTarTask extends DefaultTask implements JibTask {\n\n  private static final String HELPFUL_SUGGESTIONS_PREFIX = \"Building image tarball failed\";\n\n  @Nullable private JibExtension jibExtension;\n\n  /**\n   * This will call the property {@code \"jib\"} so that it is the same name as the extension. This\n   * way, the user would see error messages for missing configuration with the prefix {@code jib.}.\n   *\n   * @return the {@link JibExtension}.\n   */\n  @Nested\n  @Nullable\n  public JibExtension getJib() {\n    return jibExtension;\n  }\n\n  /**\n   * The target image can be overridden with the {@code --image} command line option.\n   *\n   * @param targetImage the name of the 'to' image.\n   */\n  @Option(option = \"image\", description = \"The image reference for the target image\")\n  public void setTargetImage(String targetImage) {\n    Preconditions.checkNotNull(jibExtension).getTo().setImage(targetImage);\n  }\n\n  /**\n   * Returns a collection of all the files that jib includes in the image. Only used to calculate\n   * UP-TO-DATE.\n   *\n   * @return a list of paths of input files\n   */\n  @InputFiles\n  public FileCollection getInputFiles() {\n    List<Path> extraDirectories =\n        Preconditions.checkNotNull(jibExtension).getExtraDirectories().getPaths().stream()\n            .map(ExtraDirectoryParameters::getFrom)\n            .collect(Collectors.toList());\n    return GradleProjectProperties.getInputFiles(\n        getProject(), extraDirectories, jibExtension.getConfigurationName().get());\n  }\n\n  /**\n   * The output file to check for task up-to-date.\n   *\n   * @return the output path\n   */\n  @OutputFile\n  public String getOutputFile() {\n    return Preconditions.checkNotNull(jibExtension).getOutputPaths().getTarPath().toString();\n  }\n\n  /**\n   * Task Action, builds an image to tar file.\n   *\n   * @throws IOException if an error occurs creating the jib runner\n   * @throws BuildStepsExecutionException if an error occurs while executing build steps\n   * @throws CacheDirectoryCreationException if a new cache directory could not be created\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidGlobalConfigException if the global config file is invalid\n   */\n  @TaskAction\n  public void buildTar()\n      throws BuildStepsExecutionException, IOException, CacheDirectoryCreationException,\n          MainClassInferenceException, InvalidGlobalConfigException {\n    // Asserts required @Input parameters are not null.\n    Preconditions.checkNotNull(jibExtension);\n    TaskCommon.disableHttpLogging();\n    TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n\n    GradleProjectProperties projectProperties =\n        GradleProjectProperties.getForProject(\n            getProject(),\n            getLogger(),\n            tempDirectoryProvider,\n            jibExtension.getConfigurationName().get());\n    GlobalConfig globalConfig = GlobalConfig.readConfig();\n    Future<Optional<String>> updateCheckFuture =\n        TaskCommon.newUpdateChecker(projectProperties, globalConfig, getLogger());\n    try {\n      PluginConfigurationProcessor.createJibBuildRunnerForTarImage(\n              new GradleRawConfiguration(jibExtension),\n              ignored -> Optional.empty(),\n              projectProperties,\n              globalConfig,\n              new GradleHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))\n          .runBuild();\n\n    } catch (InvalidAppRootException ex) {\n      throw new GradleException(\n          \"container.appRoot is not an absolute Unix-style path: \" + ex.getInvalidPathValue(), ex);\n\n    } catch (InvalidContainerizingModeException ex) {\n      throw new GradleException(\n          \"invalid value for containerizingMode: \" + ex.getInvalidContainerizingMode(), ex);\n\n    } catch (InvalidWorkingDirectoryException ex) {\n      throw new GradleException(\n          \"container.workingDirectory is not an absolute Unix-style path: \"\n              + ex.getInvalidPathValue(),\n          ex);\n    } catch (InvalidPlatformException ex) {\n      throw new GradleException(\n          \"from.platforms contains a platform configuration that is missing required values or has invalid values: \"\n              + ex.getMessage()\n              + \": \"\n              + ex.getInvalidPlatform(),\n          ex);\n\n    } catch (InvalidContainerVolumeException ex) {\n      throw new GradleException(\n          \"container.volumes is not an absolute Unix-style path: \" + ex.getInvalidVolume(), ex);\n\n    } catch (InvalidFilesModificationTimeException ex) {\n      throw new GradleException(\n          \"container.filesModificationTime should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or special keyword \\\"EPOCH_PLUS_SECOND\\\": \"\n              + ex.getInvalidFilesModificationTime(),\n          ex);\n\n    } catch (InvalidCreationTimeException ex) {\n      throw new GradleException(\n          \"container.creationTime should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or a special keyword (\\\"EPOCH\\\", \"\n              + \"\\\"USE_CURRENT_TIMESTAMP\\\"): \"\n              + ex.getInvalidCreationTime(),\n          ex);\n\n    } catch (JibPluginExtensionException ex) {\n      String extensionName = ex.getExtensionClass().getName();\n      throw new GradleException(\n          \"error running extension '\" + extensionName + \"': \" + ex.getMessage(), ex);\n\n    } catch (IncompatibleBaseImageJavaVersionException ex) {\n      throw new GradleException(\n          HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForGradle(\n              ex.getBaseImageMajorJavaVersion(), ex.getProjectMajorJavaVersion()),\n          ex);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new GradleException(\n          HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex);\n\n    } catch (ExtraDirectoryNotFoundException ex) {\n      throw new GradleException(\n          \"extraDirectories.paths contain \\\"from\\\" directory that doesn't exist locally: \"\n              + ex.getPath(),\n          ex);\n    } finally {\n      tempDirectoryProvider.close();\n      TaskCommon.finishUpdateChecker(projectProperties, updateCheckFuture);\n      projectProperties.waitForLoggingThread();\n    }\n  }\n\n  @Override\n  public BuildTarTask setJibExtension(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n    return this;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.common.base.Preconditions;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport javax.inject.Inject;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.MapProperty;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Optional;\n\n/**\n * A bean that configures properties of the container run from the image. This is configurable with\n * Groovy closures and can be validated when used as a task input.\n */\npublic class ContainerParameters {\n\n  private final ListProperty<String> jvmFlags;\n  private Map<String, String> environment = Collections.emptyMap();\n  private ListProperty<String> entrypoint;\n  private List<String> extraClasspath = Collections.emptyList();\n  private boolean expandClasspathDependencies;\n  private final Property<String> mainClass;\n  @Nullable private List<String> args;\n  private ImageFormat format = ImageFormat.Docker;\n  private List<String> ports = Collections.emptyList();\n  private List<String> volumes = Collections.emptyList();\n  private MapProperty<String, String> labels;\n  private String appRoot = \"\";\n  @Nullable private String user;\n  @Nullable private String workingDirectory;\n  private final Property<String> filesModificationTime;\n  private final Property<String> creationTime;\n\n  @Inject\n  public ContainerParameters(ObjectFactory objectFactory) {\n    labels = objectFactory.mapProperty(String.class, String.class).empty();\n    filesModificationTime = objectFactory.property(String.class).convention(\"EPOCH_PLUS_SECOND\");\n    creationTime = objectFactory.property(String.class).convention(\"EPOCH\");\n    mainClass = objectFactory.property(String.class);\n    jvmFlags = objectFactory.listProperty(String.class);\n    entrypoint = objectFactory.listProperty(String.class);\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public List<String> getEntrypoint() {\n    if (System.getProperty(PropertyNames.CONTAINER_ENTRYPOINT) != null) {\n      return ConfigurationPropertyValidator.parseListProperty(\n          System.getProperty(PropertyNames.CONTAINER_ENTRYPOINT));\n    }\n    return entrypoint.getOrNull();\n  }\n\n  public void setEntrypoint(List<String> entrypoint) {\n    this.entrypoint.set(entrypoint);\n  }\n\n  public void setEntrypoint(Provider<List<String>> entrypoint) {\n    this.entrypoint.set(entrypoint);\n  }\n\n  public void setEntrypoint(String entrypoint) {\n    this.entrypoint.set(Collections.singletonList(entrypoint));\n  }\n\n  @Input\n  @Optional\n  public List<String> getJvmFlags() {\n    String jvmFlagsSystemProperty = System.getProperty(PropertyNames.CONTAINER_JVM_FLAGS);\n    if (jvmFlagsSystemProperty != null) {\n      return ConfigurationPropertyValidator.parseListProperty(jvmFlagsSystemProperty);\n    }\n    return jvmFlags.getOrElse(Collections.emptyList());\n  }\n\n  public void setJvmFlags(List<String> jvmFlags) {\n    this.jvmFlags.set(jvmFlags);\n  }\n\n  public void setJvmFlags(Provider<List<String>> jvmFlags) {\n    this.jvmFlags.set(jvmFlags);\n  }\n\n  @Input\n  @Optional\n  public Map<String, String> getEnvironment() {\n    if (System.getProperty(PropertyNames.CONTAINER_ENVIRONMENT) != null) {\n      return ConfigurationPropertyValidator.parseMapProperty(\n          System.getProperty(PropertyNames.CONTAINER_ENVIRONMENT));\n    }\n    return environment;\n  }\n\n  public void setEnvironment(Map<String, String> environment) {\n    this.environment = environment;\n  }\n\n  @Input\n  @Optional\n  public List<String> getExtraClasspath() {\n    if (System.getProperty(PropertyNames.CONTAINER_EXTRA_CLASSPATH) != null) {\n      return ConfigurationPropertyValidator.parseListProperty(\n          System.getProperty(PropertyNames.CONTAINER_EXTRA_CLASSPATH));\n    }\n    return extraClasspath;\n  }\n\n  public void setExtraClasspath(List<String> classpath) {\n    extraClasspath = classpath;\n  }\n\n  @Input\n  public boolean getExpandClasspathDependencies() {\n    if (System.getProperty(PropertyNames.EXPAND_CLASSPATH_DEPENDENCIES) != null) {\n      return Boolean.valueOf(System.getProperty(PropertyNames.EXPAND_CLASSPATH_DEPENDENCIES));\n    }\n    return expandClasspathDependencies;\n  }\n\n  public void setExpandClasspathDependencies(boolean expand) {\n    expandClasspathDependencies = expand;\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public String getMainClass() {\n    String mainClassProperty = System.getProperty(PropertyNames.CONTAINER_MAIN_CLASS);\n    if (mainClassProperty != null) {\n      return mainClassProperty;\n    }\n    return mainClass.getOrNull();\n  }\n\n  public void setMainClass(String mainClass) {\n    this.mainClass.set(mainClass);\n  }\n\n  public void setMainClass(Provider<String> mainClass) {\n    this.mainClass.set(mainClass);\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public List<String> getArgs() {\n    if (System.getProperty(PropertyNames.CONTAINER_ARGS) != null) {\n      return ConfigurationPropertyValidator.parseListProperty(\n          System.getProperty(PropertyNames.CONTAINER_ARGS));\n    }\n    return args;\n  }\n\n  public void setArgs(List<String> args) {\n    this.args = args;\n  }\n\n  @Input\n  @Optional\n  public ImageFormat getFormat() {\n    if (System.getProperty(PropertyNames.CONTAINER_FORMAT) != null) {\n      return ImageFormat.valueOf(System.getProperty(PropertyNames.CONTAINER_FORMAT));\n    }\n    return Preconditions.checkNotNull(format);\n  }\n\n  public void setFormat(ImageFormat format) {\n    this.format = format;\n  }\n\n  public void setFormat(String format) {\n    this.format = ImageFormat.valueOf(format);\n  }\n\n  @Input\n  @Optional\n  public List<String> getPorts() {\n    if (System.getProperty(PropertyNames.CONTAINER_PORTS) != null) {\n      return ConfigurationPropertyValidator.parseListProperty(\n          System.getProperty(PropertyNames.CONTAINER_PORTS));\n    }\n    return ports;\n  }\n\n  public void setPorts(List<String> ports) {\n    this.ports = ports;\n  }\n\n  @Input\n  @Optional\n  public List<String> getVolumes() {\n    if (System.getProperty(PropertyNames.CONTAINER_VOLUMES) != null) {\n      return ConfigurationPropertyValidator.parseListProperty(\n          System.getProperty(PropertyNames.CONTAINER_VOLUMES));\n    }\n    return volumes;\n  }\n\n  public void setVolumes(List<String> volumes) {\n    this.volumes = volumes;\n  }\n\n  @Input\n  @Optional\n  public MapProperty<String, String> getLabels() {\n    String labelsProperty = System.getProperty(PropertyNames.CONTAINER_LABELS);\n    if (labelsProperty != null) {\n      Map<String, String> parsedLabels =\n          ConfigurationPropertyValidator.parseMapProperty(labelsProperty);\n      if (!parsedLabels.equals(labels.get())) {\n        labels.set(parsedLabels);\n      }\n    }\n    return labels;\n  }\n\n  @Input\n  @Optional\n  public String getAppRoot() {\n    if (System.getProperty(PropertyNames.CONTAINER_APP_ROOT) != null) {\n      return System.getProperty(PropertyNames.CONTAINER_APP_ROOT);\n    }\n    return appRoot;\n  }\n\n  public void setAppRoot(String appRoot) {\n    this.appRoot = appRoot;\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public String getUser() {\n    if (System.getProperty(PropertyNames.CONTAINER_USER) != null) {\n      return System.getProperty(PropertyNames.CONTAINER_USER);\n    }\n    return user;\n  }\n\n  public void setUser(String user) {\n    this.user = user;\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public String getWorkingDirectory() {\n    if (System.getProperty(PropertyNames.CONTAINER_WORKING_DIRECTORY) != null) {\n      return System.getProperty(PropertyNames.CONTAINER_WORKING_DIRECTORY);\n    }\n    return workingDirectory;\n  }\n\n  public void setWorkingDirectory(String workingDirectory) {\n    this.workingDirectory = workingDirectory;\n  }\n\n  @Input\n  @Optional\n  public Property<String> getFilesModificationTime() {\n    String property = System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME);\n    if (property != null && !property.equals(filesModificationTime.get())) {\n      filesModificationTime.set(property);\n    }\n    return filesModificationTime;\n  }\n\n  @Input\n  @Optional\n  public Property<String> getCreationTime() {\n    String property = System.getProperty(PropertyNames.CONTAINER_CREATION_TIME);\n    if (property != null && !property.equals(creationTime.get())) {\n      creationTime.set(property);\n    }\n    return creationTime;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/CredHelperParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport javax.inject.Inject;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.MapProperty;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\nimport org.gradle.api.tasks.Optional;\n\n/** Configuration for a credential helper. */\npublic class CredHelperParameters implements CredHelperConfiguration {\n  private final String propertyName;\n  private final MapProperty<String, String> environment;\n  @Nullable private String helper;\n\n  @Inject\n  public CredHelperParameters(ObjectFactory objectFactory, String propertyName) {\n    this.propertyName = propertyName;\n    environment = objectFactory.mapProperty(String.class, String.class).empty();\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public String getHelper() {\n    if (System.getProperty(propertyName) != null) {\n      return System.getProperty(propertyName);\n    }\n    return helper;\n  }\n\n  @Internal\n  @Override\n  public java.util.Optional<String> getHelperName() {\n    return java.util.Optional.ofNullable(getHelper());\n  }\n\n  public void setHelper(String helper) {\n    this.helper = helper;\n  }\n\n  @Override\n  @Input\n  @Optional\n  public Map<String, String> getEnvironment() {\n    return environment.get();\n  }\n\n  public void setEnvironment(Map<String, String> environment) {\n    this.environment.set(environment);\n  }\n\n  public void setEnvironment(Provider<Map<String, String>> environment) {\n    this.environment.set(environment);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/DockerClientParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\nimport org.gradle.api.tasks.Optional;\n\n/**\n * Object that configures the Docker executable and the additional environment variables to use when\n * executing the executable.\n */\npublic class DockerClientParameters {\n\n  @Nullable private Path executable;\n  private Map<String, String> environment = Collections.emptyMap();\n\n  @Input\n  @Nullable\n  @Optional\n  public String getExecutable() {\n    if (System.getProperty(PropertyNames.DOCKER_CLIENT_EXECUTABLE) != null) {\n      return System.getProperty(PropertyNames.DOCKER_CLIENT_EXECUTABLE);\n    }\n    return executable == null ? null : executable.toString();\n  }\n\n  @Internal\n  @Nullable\n  Path getExecutablePath() {\n    if (System.getProperty(PropertyNames.DOCKER_CLIENT_EXECUTABLE) != null) {\n      return Paths.get(System.getProperty(PropertyNames.DOCKER_CLIENT_EXECUTABLE));\n    }\n    return executable;\n  }\n\n  public void setExecutable(String executable) {\n    this.executable = Paths.get(executable);\n  }\n\n  @Input\n  @Optional\n  public Map<String, String> getEnvironment() {\n    if (System.getProperty(PropertyNames.DOCKER_CLIENT_ENVIRONMENT) != null) {\n      return ConfigurationPropertyValidator.parseMapProperty(\n          System.getProperty(PropertyNames.DOCKER_CLIENT_ENVIRONMENT));\n    }\n    return environment;\n  }\n\n  public void setEnvironment(Map<String, String> environment) {\n    this.environment = environment;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtensionParameters.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport java.util.Collections;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.gradle.api.Action;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\n\n/** Configuration of a plugin extension. */\npublic class ExtensionParameters implements ExtensionConfiguration {\n\n  private String implementation = \"<extension implementation not configured>\";\n  private Map<String, String> properties = Collections.emptyMap();\n  @Nullable private Action<?> action;\n\n  @Input\n  public String getImplementation() {\n    return getExtensionClass();\n  }\n\n  @Internal\n  @Override\n  public String getExtensionClass() {\n    return implementation;\n  }\n\n  public void setImplementation(String implementation) {\n    this.implementation = implementation;\n  }\n\n  @Input\n  @Override\n  public Map<String, String> getProperties() {\n    return properties;\n  }\n\n  public void setProperties(Map<String, String> properties) {\n    this.properties = properties;\n  }\n\n  @Internal\n  @Override\n  public java.util.Optional<Object> getExtraConfiguration() {\n    return java.util.Optional.ofNullable(action);\n  }\n\n  public void configuration(Action<?> action) {\n    this.action = action;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtensionParametersSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.ListProperty;\n\n/** Allows to add {@link ExtensionParameters} objects to the list property of the same type. */\npublic class ExtensionParametersSpec {\n\n  private final ObjectFactory objectFactory;\n  private final ListProperty<ExtensionParameters> pluginExtensions;\n\n  @Inject\n  public ExtensionParametersSpec(\n      ObjectFactory objectFactory, ListProperty<ExtensionParameters> pluginExtensions) {\n    this.objectFactory = objectFactory;\n    this.pluginExtensions = pluginExtensions;\n  }\n\n  /**\n   * Adds a new plugin extension configuration to the extensions list.\n   *\n   * @param action closure representing an extension configuration\n   */\n  public void pluginExtension(Action<? super ExtensionParameters> action) {\n    ExtensionParameters extension = objectFactory.newInstance(ExtensionParameters.class);\n    action.execute(extension);\n    pluginExtensions.add(extension);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoriesParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nonnull;\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.Project;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.MapProperty;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\n\n/** Object in {@link JibExtension} that configures the extra directories. */\npublic class ExtraDirectoriesParameters {\n\n  private final ObjectFactory objects;\n  private final Project project;\n\n  private ListProperty<ExtraDirectoryParameters> paths;\n  private ExtraDirectoryParametersSpec spec;\n  private MapProperty<String, String> permissions;\n\n  @Inject\n  public ExtraDirectoriesParameters(ObjectFactory objects, Project project) {\n    this.objects = objects;\n    this.project = project;\n    paths = objects.listProperty(ExtraDirectoryParameters.class).empty();\n    spec = objects.newInstance(ExtraDirectoryParametersSpec.class, project, paths);\n    permissions = objects.mapProperty(String.class, String.class).empty();\n  }\n\n  public void paths(Action<? super ExtraDirectoryParametersSpec> action) {\n    action.execute(spec);\n  }\n\n  @Input\n  public List<String> getPathStrings() {\n    // Gradle warns about @Input annotations on File objects, so we have to expose a getter for a\n    // String to make them go away.\n    return getPaths().stream()\n        .map(extraDirectoryParameters -> extraDirectoryParameters.getFrom().toString())\n        .collect(Collectors.toList());\n  }\n\n  @Internal\n  public List<ExtraDirectoryParameters> getPaths() {\n    // Gradle warns about @Input annotations on File objects, so we have to expose a getter for a\n    // String to make them go away.\n    String property = System.getProperty(PropertyNames.EXTRA_DIRECTORIES_PATHS);\n    if (property != null) {\n      List<String> pathStrings = ConfigurationPropertyValidator.parseListProperty(property);\n      return pathStrings.stream()\n          .map(path -> new ExtraDirectoryParameters(objects, project, Paths.get(path), \"/\"))\n          .collect(Collectors.toList());\n    }\n    if (paths.get().isEmpty()) {\n      return Collections.singletonList(\n          new ExtraDirectoryParameters(\n              objects,\n              project,\n              project.getProjectDir().toPath().resolve(\"src\").resolve(\"main\").resolve(\"jib\"),\n              \"/\"));\n    }\n    return paths.get();\n  }\n\n  /**\n   * Sets paths. {@code paths} can be any suitable object describing file paths convertible by\n   * {@link Project#files} (such as {@link File}, {@code List<File>}, or {@code List<String>}).\n   *\n   * @param paths paths to set.\n   */\n  public void setPaths(Object paths) {\n    this.paths.set(convertToExtraDirectoryParametersList(paths));\n  }\n\n  /**\n   * Sets paths, for lazy evaluation where {@code paths} is a {@link Provider} of a suitable object.\n   *\n   * @param paths provider of paths to set\n   * @see #setPaths(Object)\n   */\n  public void setPaths(Provider<Object> paths) {\n    this.paths.set(paths.map(this::convertToExtraDirectoryParametersList));\n  }\n\n  /**\n   * Helper method to convert {@code Object} to {@code List<ExtraDirectoryParameters>} in {@code\n   * setFrom}.\n   */\n  @Nonnull\n  private List<ExtraDirectoryParameters> convertToExtraDirectoryParametersList(Object obj) {\n    return project.files(obj).getFiles().stream()\n        .map(file -> new ExtraDirectoryParameters(objects, project, file.toPath(), \"/\"))\n        .collect(Collectors.toList());\n  }\n\n  /**\n   * Gets the permissions for files in the extra layer on the container. Maps from absolute path on\n   * the container to a 3-digit octal string representation of the file permission bits (e.g. {@code\n   * \"/path/on/container\" -> \"755\"}).\n   *\n   * @return the permissions map from path on container to file permissions\n   */\n  @Input\n  public MapProperty<String, String> getPermissions() {\n    String property = System.getProperty(PropertyNames.EXTRA_DIRECTORIES_PERMISSIONS);\n    if (property != null) {\n      Map<String, String> parsedPermissions =\n          ConfigurationPropertyValidator.parseMapProperty(property);\n      if (!parsedPermissions.equals(permissions.get())) {\n        permissions.set(parsedPermissions);\n      }\n    }\n    return permissions;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoryParameters.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport javax.inject.Inject;\nimport org.gradle.api.Project;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\n\n/** Configuration of an extra directory. */\npublic class ExtraDirectoryParameters implements ExtraDirectoriesConfiguration {\n\n  private Project project;\n  private Property<Path> from;\n  private Property<String> into;\n  private ListProperty<String> includes;\n  private ListProperty<String> excludes;\n\n  @Inject\n  public ExtraDirectoryParameters(ObjectFactory objects, Project project) {\n    this.project = project;\n    this.from = objects.property(Path.class).value(Paths.get(\"\"));\n    this.into = objects.property(String.class).value(\"/\");\n    this.includes = objects.listProperty(String.class).empty();\n    this.excludes = objects.listProperty(String.class).empty();\n  }\n\n  ExtraDirectoryParameters(ObjectFactory objects, Project project, Path from, String into) {\n    this(objects, project);\n    this.from = objects.property(Path.class).value(from);\n    this.into = objects.property(String.class).value(into);\n  }\n\n  @Input\n  public String getFromString() {\n    // Gradle warns about @Input annotations on File objects, so we have to expose a getter for a\n    // String to make them go away.\n    return from.get().toString();\n  }\n\n  @Override\n  @Internal\n  public Path getFrom() {\n    return from.get();\n  }\n\n  public void setFrom(Object from) {\n    this.from.set(project.file(from).toPath());\n  }\n\n  public void setFrom(Provider<Object> from) {\n    this.from.set(from.map(obj -> project.file(obj).toPath()));\n  }\n\n  @Override\n  @Input\n  public String getInto() {\n    return into.get();\n  }\n\n  public void setInto(String into) {\n    this.into.set(into);\n  }\n\n  public void setInto(Provider<String> into) {\n    this.into.set(into);\n  }\n\n  @Input\n  public ListProperty<String> getIncludes() {\n    return includes;\n  }\n\n  @Input\n  public ListProperty<String> getExcludes() {\n    return excludes;\n  }\n\n  @Override\n  @Internal\n  public List<String> getIncludesList() {\n    return includes.get();\n  }\n\n  @Override\n  @Internal\n  public List<String> getExcludesList() {\n    return excludes.get();\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoryParametersSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.Project;\nimport org.gradle.api.provider.ListProperty;\n\n/** Allows to add {@link ExtraDirectoryParameters} objects to the list property of the same type. */\npublic class ExtraDirectoryParametersSpec {\n\n  private final Project project;\n  private final ListProperty<ExtraDirectoryParameters> paths;\n\n  @Inject\n  public ExtraDirectoryParametersSpec(\n      Project project, ListProperty<ExtraDirectoryParameters> paths) {\n    this.project = project;\n    this.paths = paths;\n  }\n\n  /**\n   * Adds a new extra directory configuration to the list.\n   *\n   * @param action closure representing an extra directory configuration\n   */\n  public void path(Action<? super ExtraDirectoryParameters> action) {\n    ExtraDirectoryParameters extraDirectory =\n        project.getObjects().newInstance(ExtraDirectoryParameters.class, project);\n    action.execute(extraDirectory);\n    paths.add(extraDirectory);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleHelpfulSuggestions.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\n\n/** Gradle-specific {@link HelpfulSuggestions}. */\nclass GradleHelpfulSuggestions extends HelpfulSuggestions {\n\n  GradleHelpfulSuggestions(String messagePrefix) {\n    super(messagePrefix, \"gradle clean\", \"jib.to.image\", \"--image\", \"build.gradle\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport com.google.cloud.tools.jib.event.progress.ProgressEventHandler;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.gradle.extension.JibGradlePluginExtension;\nimport com.google.cloud.tools.jib.plugins.common.ContainerizingMode;\nimport com.google.cloud.tools.jib.plugins.common.JavaContainerBuilderHelper;\nimport com.google.cloud.tools.jib.plugins.common.PluginExtensionLogger;\nimport com.google.cloud.tools.jib.plugins.common.ProjectProperties;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.TimerEventHandler;\nimport com.google.cloud.tools.jib.plugins.common.ZipUtil;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLoggerBuilder;\nimport com.google.cloud.tools.jib.plugins.common.logging.ProgressDisplayGenerator;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.cloud.tools.jib.plugins.extension.NullExtension;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Optional;\nimport java.util.ServiceLoader;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.apache.tools.ant.taskdefs.condition.Os;\nimport org.gradle.api.Action;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.JavaVersion;\nimport org.gradle.api.Project;\nimport org.gradle.api.Task;\nimport org.gradle.api.artifacts.ResolvedArtifact;\nimport org.gradle.api.artifacts.component.ProjectComponentIdentifier;\nimport org.gradle.api.file.FileCollection;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.plugins.JavaPluginExtension;\nimport org.gradle.api.plugins.WarPlugin;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.tasks.SourceSet;\nimport org.gradle.api.tasks.SourceSetContainer;\nimport org.gradle.api.tasks.TaskProvider;\nimport org.gradle.jvm.tasks.Jar;\n\n/** Obtains information about a Gradle {@link Project} that uses Jib. */\npublic class GradleProjectProperties implements ProjectProperties {\n\n  /** Used to generate the User-Agent header and history metadata. */\n  private static final String TOOL_NAME = \"jib-gradle-plugin\";\n\n  /** Used to generate the User-Agent header and history metadata and verify versions. */\n  static final String TOOL_VERSION =\n      GradleProjectProperties.class.getPackage().getImplementationVersion();\n\n  /** Used for logging during main class inference. */\n  private static final String PLUGIN_NAME = \"jib\";\n\n  /** Used for logging during main class inference. */\n  private static final String JAR_PLUGIN_NAME = \"'jar' task\";\n\n  /** Name of the `main` {@link SourceSet} to use as source files. */\n  private static final String MAIN_SOURCE_SET_NAME = \"main\";\n\n  private static final Duration LOGGING_THREAD_SHUTDOWN_TIMEOUT = Duration.ofSeconds(1);\n\n  /**\n   * Generate an instance for a gradle project.\n   *\n   * @param project a gradle project\n   * @param logger a gradle logging instance to use for logging during the build\n   * @param tempDirectoryProvider for scratch space during the build\n   * @param configurationName the configuration of which the dependencies should be packed into the\n   *     container\n   * @return a GradleProjectProperties instance to use in a jib build\n   */\n  public static GradleProjectProperties getForProject(\n      Project project,\n      Logger logger,\n      TempDirectoryProvider tempDirectoryProvider,\n      String configurationName) {\n    Supplier<List<JibGradlePluginExtension<?>>> extensionLoader =\n        () -> {\n          List<JibGradlePluginExtension<?>> extensions = new ArrayList<>();\n          for (JibGradlePluginExtension<?> extension :\n              ServiceLoader.load(JibGradlePluginExtension.class)) {\n            extensions.add(extension);\n          }\n          return extensions;\n        };\n    return new GradleProjectProperties(\n        project, logger, tempDirectoryProvider, extensionLoader, configurationName);\n  }\n\n  String getWarFilePath() {\n    TaskProvider<Task> bootWarTask = TaskCommon.getBootWarTaskProvider(project);\n    if (bootWarTask != null && bootWarTask.get().getEnabled()) {\n      return bootWarTask.get().getOutputs().getFiles().getAsPath();\n    }\n\n    TaskProvider<Task> warTask = TaskCommon.getWarTaskProvider(project);\n    return Verify.verifyNotNull(warTask).get().getOutputs().getFiles().getAsPath();\n  }\n\n  private static boolean isProgressFooterEnabled(Project project) {\n    if (\"plain\".equals(System.getProperty(PropertyNames.CONSOLE))) {\n      return false;\n    }\n\n    switch (project.getGradle().getStartParameter().getConsoleOutput()) {\n      case Plain:\n        return false;\n\n      case Auto:\n        // Enables progress footer when ANSI is supported (Windows or TERM not 'dumb').\n        // Unlike jib-maven-plugin, we cannot test \"System.console() != null\".\n        // https://github.com/GoogleContainerTools/jib/issues/2920#issuecomment-749234458\n        return Os.isFamily(Os.FAMILY_WINDOWS) || !\"dumb\".equals(System.getenv(\"TERM\"));\n\n      default:\n        return true;\n    }\n  }\n\n  private final Project project;\n  private final SingleThreadedExecutor singleThreadedExecutor = new SingleThreadedExecutor();\n  private final ConsoleLogger consoleLogger;\n  private final TempDirectoryProvider tempDirectoryProvider;\n  private final Supplier<List<JibGradlePluginExtension<?>>> extensionLoader;\n  private final String configurationName;\n\n  @VisibleForTesting\n  GradleProjectProperties(\n      Project project,\n      Logger logger,\n      TempDirectoryProvider tempDirectoryProvider,\n      Supplier<List<JibGradlePluginExtension<?>>> extensionLoader,\n      String configurationName) {\n    this.project = project;\n    this.tempDirectoryProvider = tempDirectoryProvider;\n    this.extensionLoader = extensionLoader;\n    this.configurationName = configurationName;\n    ConsoleLoggerBuilder consoleLoggerBuilder =\n        (isProgressFooterEnabled(project)\n                ? ConsoleLoggerBuilder.rich(singleThreadedExecutor, false)\n                : ConsoleLoggerBuilder.plain(singleThreadedExecutor).progress(logger::lifecycle))\n            .lifecycle(logger::lifecycle);\n    if (logger.isDebugEnabled()) {\n      consoleLoggerBuilder.debug(logger::debug);\n    }\n    if (logger.isInfoEnabled()) {\n      consoleLoggerBuilder.info(logger::info);\n    }\n    if (logger.isWarnEnabled()) {\n      consoleLoggerBuilder.warn(logger::warn);\n    }\n    if (logger.isErrorEnabled()) {\n      consoleLoggerBuilder.error(logger::error);\n    }\n    consoleLogger = consoleLoggerBuilder.build();\n  }\n\n  @Override\n  public JibContainerBuilder createJibContainerBuilder(\n      JavaContainerBuilder javaContainerBuilder, ContainerizingMode containerizingMode) {\n    try {\n      FileCollection projectDependencies =\n          project.files(\n              project.getConfigurations().getByName(configurationName).getResolvedConfiguration()\n                  .getResolvedArtifacts().stream()\n                  .filter(\n                      artifact ->\n                          artifact.getId().getComponentIdentifier()\n                              instanceof ProjectComponentIdentifier)\n                  .map(ResolvedArtifact::getFile)\n                  .collect(Collectors.toList()));\n\n      if (isWarProject()) {\n        String warFilePath = getWarFilePath();\n        log(LogEvent.info(\"WAR project identified, creating WAR image from: \" + warFilePath));\n        Path explodedWarPath = tempDirectoryProvider.newDirectory();\n        ZipUtil.unzip(Paths.get(warFilePath), explodedWarPath);\n        return JavaContainerBuilderHelper.fromExplodedWar(\n            javaContainerBuilder,\n            explodedWarPath,\n            projectDependencies.getFiles().stream().map(File::getName).collect(Collectors.toSet()));\n      }\n\n      SourceSet mainSourceSet = getMainSourceSet();\n      FileCollection classesOutputDirectories =\n          mainSourceSet.getOutput().getClassesDirs().filter(File::exists);\n      Path resourcesOutputDirectory = mainSourceSet.getOutput().getResourcesDir().toPath();\n      FileCollection allFiles =\n          project.getConfigurations().getByName(configurationName).filter(File::exists);\n\n      FileCollection nonProjectDependencies =\n          allFiles\n              .minus(classesOutputDirectories)\n              .minus(projectDependencies)\n              .filter(file -> !file.toPath().equals(resourcesOutputDirectory));\n\n      FileCollection snapshotDependencies =\n          nonProjectDependencies.filter(file -> file.getName().contains(\"SNAPSHOT\"));\n      FileCollection dependencies = nonProjectDependencies.minus(snapshotDependencies);\n\n      // Adds dependency files\n      javaContainerBuilder\n          .addDependencies(\n              dependencies.getFiles().stream().map(File::toPath).collect(Collectors.toList()))\n          .addSnapshotDependencies(\n              snapshotDependencies.getFiles().stream()\n                  .map(File::toPath)\n                  .collect(Collectors.toList()))\n          .addProjectDependencies(\n              projectDependencies.getFiles().stream()\n                  .map(File::toPath)\n                  .collect(Collectors.toList()));\n\n      switch (containerizingMode) {\n        case EXPLODED:\n          // Adds resource files\n          if (Files.exists(resourcesOutputDirectory)) {\n            javaContainerBuilder.addResources(resourcesOutputDirectory);\n          }\n\n          // Adds class files\n          for (File classesOutputDirectory : classesOutputDirectories) {\n            javaContainerBuilder.addClasses(classesOutputDirectory.toPath());\n          }\n          if (classesOutputDirectories.isEmpty()) {\n            log(LogEvent.warn(\"No classes files were found - did you compile your project?\"));\n          }\n          break;\n\n        case PACKAGED:\n          // Add a JAR\n          Jar jarTask = (Jar) project.getTasks().findByName(\"jar\");\n          Path jarPath = jarTask.getArchiveFile().get().getAsFile().toPath();\n          log(LogEvent.debug(\"Using JAR: \" + jarPath));\n          javaContainerBuilder.addToClasspath(jarPath);\n          break;\n\n        default:\n          throw new IllegalStateException(\"unknown containerizing mode: \" + containerizingMode);\n      }\n\n      return javaContainerBuilder.toContainerBuilder();\n\n    } catch (IOException ex) {\n      throw new GradleException(\"Obtaining project build output files failed\", ex);\n    }\n  }\n\n  @Override\n  public List<Path> getClassFiles() throws IOException {\n    // TODO: Consolidate with createJibContainerBuilder\n    FileCollection classesOutputDirectories =\n        getMainSourceSet().getOutput().getClassesDirs().filter(File::exists);\n    List<Path> classFiles = new ArrayList<>();\n    for (File classesOutputDirectory : classesOutputDirectories) {\n      classFiles.addAll(new DirectoryWalker(classesOutputDirectory.toPath()).walk());\n    }\n    return classFiles;\n  }\n\n  @Override\n  public List<Path> getDependencies() {\n    List<Path> dependencies = new ArrayList<>();\n    FileCollection runtimeClasspath = project.getConfigurations().getByName(configurationName);\n    // To be on the safe side with the order, calling \"forEach\" first (no filtering operations).\n    runtimeClasspath.forEach(\n        file -> {\n          if (file.exists()\n              && file.isFile()\n              && file.getName().toLowerCase(Locale.US).endsWith(\".jar\")) {\n            dependencies.add(file.toPath());\n          }\n        });\n    return dependencies;\n  }\n\n  @Override\n  public void waitForLoggingThread() {\n    singleThreadedExecutor.shutDownAndAwaitTermination(LOGGING_THREAD_SHUTDOWN_TIMEOUT);\n  }\n\n  @Override\n  public void configureEventHandlers(Containerizer containerizer) {\n    containerizer\n        .addEventHandler(LogEvent.class, this::log)\n        .addEventHandler(\n            TimerEvent.class, new TimerEventHandler(message -> log(LogEvent.debug(message))))\n        .addEventHandler(\n            ProgressEvent.class,\n            new ProgressEventHandler(\n                update -> {\n                  List<String> footer =\n                      ProgressDisplayGenerator.generateProgressDisplay(\n                          update.getProgress(), update.getUnfinishedLeafTasks());\n                  footer.add(\"\");\n                  consoleLogger.setFooter(footer);\n                }));\n  }\n\n  @Override\n  public void log(LogEvent logEvent) {\n    consoleLogger.log(logEvent.getLevel(), logEvent.getMessage());\n  }\n\n  @Override\n  public String getToolName() {\n    return TOOL_NAME;\n  }\n\n  @Override\n  public String getToolVersion() {\n    return TOOL_VERSION;\n  }\n\n  @Override\n  public String getPluginName() {\n    return PLUGIN_NAME;\n  }\n\n  @Nullable\n  @Override\n  public String getMainClassFromJarPlugin() {\n    Jar jarTask = (Jar) project.getTasks().findByName(\"jar\");\n    if (jarTask == null) {\n      return null;\n    }\n\n    Object value = jarTask.getManifest().getAttributes().get(\"Main-Class\");\n\n    if (value instanceof Provider) {\n      value = ((Provider<?>) value).getOrNull();\n    }\n\n    if (value instanceof String) {\n      return (String) value;\n    }\n\n    if (value == null) {\n      return null;\n    }\n\n    return String.valueOf(value);\n  }\n\n  @Override\n  public Path getDefaultCacheDirectory() {\n    return project.getBuildDir().toPath().resolve(CACHE_DIRECTORY_NAME);\n  }\n\n  @Override\n  public String getJarPluginName() {\n    return JAR_PLUGIN_NAME;\n  }\n\n  @Override\n  public boolean isWarProject() {\n    return project.getPlugins().hasPlugin(WarPlugin.class);\n  }\n\n  /**\n   * Returns the input files for a task. These files include the gradle {@link\n   * org.gradle.api.artifacts.Configuration}, output directories (classes, resources, etc.) of the\n   * main {@link org.gradle.api.tasks.SourceSet}, and any extraDirectories defined by the user to\n   * include in the container.\n   *\n   * @param project the gradle project\n   * @param extraDirectories the image's configured extra directories\n   * @return the input files\n   */\n  @VisibleForTesting\n  static FileCollection getInputFiles(\n      Project project, List<Path> extraDirectories, String configurationName) {\n    List<FileCollection> dependencyFileCollections = new ArrayList<>();\n    dependencyFileCollections.add(project.getConfigurations().getByName(configurationName));\n    // Output directories (classes and resources) from main SourceSet are added\n    // so that BuildTarTask picks up changes in these and do not skip task\n    SourceSetContainer sourceSetContainer =\n        project.getExtensions().getByType(SourceSetContainer.class);\n    SourceSet mainSourceSet = sourceSetContainer.getByName(MAIN_SOURCE_SET_NAME);\n    dependencyFileCollections.add(mainSourceSet.getOutput());\n\n    extraDirectories.stream()\n        .filter(Files::exists)\n        .map(Path::toFile)\n        .map(project::files)\n        .forEach(dependencyFileCollections::add);\n\n    return project.files(dependencyFileCollections);\n  }\n\n  @Override\n  public String getName() {\n    return project.getName();\n  }\n\n  @Override\n  public String getVersion() {\n    return project.getVersion().toString();\n  }\n\n  @Override\n  public int getMajorJavaVersion() {\n    JavaVersion version = JavaVersion.current();\n    JavaPluginExtension javaPluginExtension =\n        project.getExtensions().findByType(JavaPluginExtension.class);\n    if (javaPluginExtension != null) {\n      version = javaPluginExtension.getTargetCompatibility();\n    }\n    return Integer.valueOf(version.getMajorVersion());\n  }\n\n  @Override\n  public boolean isOffline() {\n    return project.getGradle().getStartParameter().isOffline();\n  }\n\n  @Override\n  public JibContainerBuilder runPluginExtensions(\n      List<? extends ExtensionConfiguration> extensionConfigs,\n      JibContainerBuilder jibContainerBuilder)\n      throws JibPluginExtensionException {\n    if (extensionConfigs.isEmpty()) {\n      log(LogEvent.debug(\"No Jib plugin extensions configured to load\"));\n      return jibContainerBuilder;\n    }\n\n    List<JibGradlePluginExtension<?>> loadedExtensions = extensionLoader.get();\n    JibGradlePluginExtension<?> extension = null;\n    ContainerBuildPlan buildPlan = jibContainerBuilder.toContainerBuildPlan();\n    try {\n      for (ExtensionConfiguration config : extensionConfigs) {\n        extension = findConfiguredExtension(loadedExtensions, config);\n\n        log(LogEvent.lifecycle(\"Running extension: \" + config.getExtensionClass()));\n        buildPlan =\n            runPluginExtension(extension.getExtraConfigType(), extension, config, buildPlan);\n        ImageReference.parse(buildPlan.getBaseImage()); // to validate image reference\n      }\n      return jibContainerBuilder.applyContainerBuildPlan(buildPlan);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new JibPluginExtensionException(\n          Verify.verifyNotNull(extension).getClass(),\n          \"invalid base image reference: \" + buildPlan.getBaseImage(),\n          ex);\n    }\n  }\n\n  // Unchecked casting: \"getExtraConfiguration().get()\" (Object) to Action<T> and \"extension\"\n  // (JibGradlePluginExtension<?>) to JibGradlePluginExtension<T> where T is the extension-defined\n  // config type (as requested by \"JibGradlePluginExtension.getExtraConfigType()\").\n  @SuppressWarnings({\"unchecked\"})\n  private <T> ContainerBuildPlan runPluginExtension(\n      Optional<Class<T>> extraConfigType,\n      JibGradlePluginExtension<?> extension,\n      ExtensionConfiguration config,\n      ContainerBuildPlan buildPlan)\n      throws JibPluginExtensionException {\n    T extraConfig = null;\n    Optional<Object> configs = config.getExtraConfiguration();\n    if (configs.isPresent()) {\n      if (!extraConfigType.isPresent()) {\n        throw new IllegalArgumentException(\n            \"extension \"\n                + extension.getClass().getSimpleName()\n                + \" does not expect extension-specific configuration; remove the inapplicable \"\n                + \"'pluginExtension.configuration' from Gradle build script\");\n      } else {\n        // configs.get() is of type Action, so this cast always succeeds.\n        // (Note generic <T> is erased at runtime.)\n        Action<T> action = (Action<T>) configs.get();\n        extraConfig = project.getObjects().newInstance(extraConfigType.get(), project);\n        action.execute(extraConfig);\n      }\n    }\n\n    try {\n      return ((JibGradlePluginExtension<T>) extension)\n          .extendContainerBuildPlan(\n              buildPlan,\n              config.getProperties(),\n              Optional.ofNullable(extraConfig),\n              () -> project,\n              new PluginExtensionLogger(this::log));\n    } catch (RuntimeException ex) {\n      throw new JibPluginExtensionException(\n          extension.getClass(), \"extension crashed: \" + ex.getMessage(), ex);\n    }\n  }\n\n  private JibGradlePluginExtension<?> findConfiguredExtension(\n      List<JibGradlePluginExtension<?>> extensions, ExtensionConfiguration config)\n      throws JibPluginExtensionException {\n    Predicate<JibGradlePluginExtension<?>> matchesClassName =\n        extension -> extension.getClass().getName().equals(config.getExtensionClass());\n    Optional<JibGradlePluginExtension<?>> found =\n        extensions.stream().filter(matchesClassName).findFirst();\n    if (!found.isPresent()) {\n      throw new JibPluginExtensionException(\n          NullExtension.class,\n          \"extension configured but not discovered on Jib runtime classpath: \"\n              + config.getExtensionClass());\n    }\n    return found.get();\n  }\n\n  private SourceSet getMainSourceSet() {\n    SourceSetContainer sourceSetContainer =\n        project.getExtensions().getByType(SourceSetContainer.class);\n    return sourceSetContainer.getByName(MAIN_SOURCE_SET_NAME);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\n/** Gradle-specific adapter for providing raw configuration parameter values. */\npublic class GradleRawConfiguration implements RawConfiguration {\n\n  private final JibExtension jibExtension;\n\n  public GradleRawConfiguration(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n  }\n\n  @Override\n  public Optional<String> getFromImage() {\n    return Optional.ofNullable(jibExtension.getFrom().getImage());\n  }\n\n  @Override\n  public AuthProperty getFromAuth() {\n    return jibExtension.getFrom().getAuth();\n  }\n\n  @Override\n  public CredHelperConfiguration getFromCredHelper() {\n    return jibExtension.getFrom().getCredHelper();\n  }\n\n  @Override\n  public Optional<String> getToImage() {\n    return Optional.ofNullable(jibExtension.getTo().getImage());\n  }\n\n  @Override\n  public AuthProperty getToAuth() {\n    return jibExtension.getTo().getAuth();\n  }\n\n  @Override\n  public CredHelperConfiguration getToCredHelper() {\n    return jibExtension.getTo().getCredHelper();\n  }\n\n  @Override\n  public Set<String> getToTags() {\n    return jibExtension.getTo().getTags();\n  }\n\n  @Override\n  public Optional<List<String>> getEntrypoint() {\n    return Optional.ofNullable(jibExtension.getContainer().getEntrypoint());\n  }\n\n  @Override\n  public Optional<List<String>> getProgramArguments() {\n    return Optional.ofNullable(jibExtension.getContainer().getArgs());\n  }\n\n  @Override\n  public List<String> getExtraClasspath() {\n    return jibExtension.getContainer().getExtraClasspath();\n  }\n\n  @Override\n  public boolean getExpandClasspathDependencies() {\n    return jibExtension.getContainer().getExpandClasspathDependencies();\n  }\n\n  @Override\n  public Optional<String> getMainClass() {\n    return Optional.ofNullable(jibExtension.getContainer().getMainClass());\n  }\n\n  @Override\n  public List<String> getJvmFlags() {\n    return jibExtension.getContainer().getJvmFlags();\n  }\n\n  @Override\n  public String getAppRoot() {\n    return jibExtension.getContainer().getAppRoot();\n  }\n\n  @Override\n  public Map<String, String> getEnvironment() {\n    return jibExtension.getContainer().getEnvironment();\n  }\n\n  @Override\n  public Map<String, String> getLabels() {\n    return jibExtension.getContainer().getLabels().get();\n  }\n\n  @Override\n  public List<String> getVolumes() {\n    return jibExtension.getContainer().getVolumes();\n  }\n\n  @Override\n  public List<String> getPorts() {\n    return jibExtension.getContainer().getPorts();\n  }\n\n  @Override\n  public Optional<String> getUser() {\n    return Optional.ofNullable(jibExtension.getContainer().getUser());\n  }\n\n  @Override\n  public Optional<String> getWorkingDirectory() {\n    return Optional.ofNullable(jibExtension.getContainer().getWorkingDirectory());\n  }\n\n  @Override\n  public boolean getAllowInsecureRegistries() {\n    return jibExtension.getAllowInsecureRegistries();\n  }\n\n  @Override\n  public ImageFormat getImageFormat() {\n    return jibExtension.getContainer().getFormat();\n  }\n\n  @Override\n  public Optional<String> getProperty(String propertyName) {\n    return Optional.ofNullable(System.getProperty(propertyName));\n  }\n\n  @Override\n  public String getFilesModificationTime() {\n    return jibExtension.getContainer().getFilesModificationTime().get();\n  }\n\n  @Override\n  public String getCreationTime() {\n    return jibExtension.getContainer().getCreationTime().get();\n  }\n\n  @Override\n  public List<? extends ExtraDirectoriesConfiguration> getExtraDirectories() {\n    for (ExtraDirectoryParameters path : jibExtension.getExtraDirectories().getPaths()) {\n      if (path.getFrom().equals(Paths.get(\"\"))) {\n        throw new IllegalArgumentException(\n            \"Incomplete extraDirectories.paths configuration; source directory must be set\");\n      }\n    }\n    return jibExtension.getExtraDirectories().getPaths();\n  }\n\n  @Override\n  public Map<String, FilePermissions> getExtraDirectoryPermissions() {\n    return TaskCommon.convertPermissionsMap(\n        jibExtension.getExtraDirectories().getPermissions().get());\n  }\n\n  @Override\n  public Optional<Path> getDockerExecutable() {\n    return Optional.ofNullable(jibExtension.getDockerClient().getExecutablePath());\n  }\n\n  @Override\n  public Map<String, String> getDockerEnvironment() {\n    return jibExtension.getDockerClient().getEnvironment();\n  }\n\n  @Override\n  public String getContainerizingMode() {\n    return jibExtension.getContainerizingMode();\n  }\n\n  @Override\n  public Path getTarOutputPath() {\n    return jibExtension.getOutputPaths().getTarPath();\n  }\n\n  @Override\n  public Path getDigestOutputPath() {\n    return jibExtension.getOutputPaths().getDigestPath();\n  }\n\n  @Override\n  public Path getImageIdOutputPath() {\n    return jibExtension.getOutputPaths().getImageIdPath();\n  }\n\n  @Override\n  public Path getImageJsonOutputPath() {\n    return jibExtension.getOutputPaths().getImageJsonPath();\n  }\n\n  @Override\n  public List<? extends ExtensionConfiguration> getPluginExtensions() {\n    return jibExtension.getPluginExtensions().get();\n  }\n\n  @Override\n  public List<? extends PlatformConfiguration> getPlatforms() {\n    return jibExtension.getFrom().getPlatforms().get();\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibExtension.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.gradle.skaffold.SkaffoldParameters;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport org.gradle.api.Action;\nimport org.gradle.api.Project;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.plugins.JavaPlugin;\nimport org.gradle.api.provider.ListProperty;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Nested;\nimport org.gradle.api.tasks.Optional;\n\n/**\n * Plugin extension for {@link JibPlugin}.\n *\n * <p>Example configuration:\n *\n * <pre>{@code\n * jib {\n *   from {\n *     image = 'gcr.io/my-gcp-project/my-base-image'\n *     credHelper = 'gcr'\n *     platforms {\n *       platform {\n *         os = 'linux'\n *         architecture = 'amd64'\n *       }\n *     }\n *   }\n *   to {\n *     image = 'gcr.io/gcp-project/my-app:built-with-jib'\n *     credHelper = 'ecr-login'\n *   }\n *   container {\n *     jvmFlags = ['-Xms512m', '-Xdebug']\n *     mainClass = 'com.mycompany.myproject.Main'\n *     args = ['arg1', 'arg2']\n *     ports = ['1000', '2000-2010', '3000']\n *     format = OCI\n *     appRoot = '/app'\n *   }\n *   extraDirectories {\n *     paths = ['/path/to/extra/dir', 'can/be/relative/to/project/root']\n *     permissions = [\n *       '/path/on/container/file1': 744,\n *       '/path/on/container/file2': 123\n *     ]\n *   }\n *   outputPaths {\n *     tar = file('reative/to/project/root/jib-image.tar')\n *     digest = file('/absolute/path/jib-image.digest')\n *     imageId = file(\"$buildDir/jib-image.id\")\n *   }\n *   allowInsecureRegistries = false\n *   containerizingMode = 'exploded'\n *   pluginExtensions {\n *     pluginExtension {\n *       implementation = 'com.example.ThirdPartyJibGradleExtension'\n *       properties = [customKey: 'value]\n *     }\n *   }\n * }\n * }</pre>\n */\npublic class JibExtension {\n\n  // Defines default configuration values.\n  private static final boolean DEFAULT_ALLOW_INSECURE_REGISTIRIES = false;\n  private static final String DEFAULT_CONTAINERIZING_MODE = \"exploded\";\n\n  private final BaseImageParameters from;\n  private final TargetImageParameters to;\n  private final ContainerParameters container;\n  private final ExtraDirectoriesParameters extraDirectories;\n  private final DockerClientParameters dockerClient;\n  private final OutputPathsParameters outputPaths;\n  private final SkaffoldParameters skaffold;\n  private final Property<Boolean> allowInsecureRegistries;\n  private final Property<String> containerizingMode;\n  private final Property<String> configurationName;\n  private final ListProperty<ExtensionParameters> pluginExtensions;\n  private final ExtensionParametersSpec extensionParametersSpec;\n\n  /**\n   * Should be called using {@link org.gradle.api.plugins.ExtensionContainer#create}.\n   *\n   * @param project the injected gradle project\n   */\n  public JibExtension(Project project) {\n    ObjectFactory objectFactory = project.getObjects();\n\n    from = objectFactory.newInstance(BaseImageParameters.class);\n    to = objectFactory.newInstance(TargetImageParameters.class);\n    container = objectFactory.newInstance(ContainerParameters.class);\n    extraDirectories = objectFactory.newInstance(ExtraDirectoriesParameters.class, project);\n    dockerClient = objectFactory.newInstance(DockerClientParameters.class);\n    outputPaths = objectFactory.newInstance(OutputPathsParameters.class, project);\n    skaffold = objectFactory.newInstance(SkaffoldParameters.class, project);\n\n    pluginExtensions = objectFactory.listProperty(ExtensionParameters.class).empty();\n    extensionParametersSpec =\n        objectFactory.newInstance(ExtensionParametersSpec.class, pluginExtensions);\n    allowInsecureRegistries =\n        objectFactory.property(Boolean.class).convention(DEFAULT_ALLOW_INSECURE_REGISTIRIES);\n    containerizingMode =\n        objectFactory.property(String.class).convention(DEFAULT_CONTAINERIZING_MODE);\n    configurationName =\n        objectFactory\n            .property(String.class)\n            .convention(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);\n  }\n\n  public void from(Action<? super BaseImageParameters> action) {\n    action.execute(from);\n  }\n\n  public void to(Action<? super TargetImageParameters> action) {\n    action.execute(to);\n  }\n\n  public void container(Action<? super ContainerParameters> action) {\n    action.execute(container);\n  }\n\n  public void extraDirectories(Action<? super ExtraDirectoriesParameters> action) {\n    action.execute(extraDirectories);\n  }\n\n  public void dockerClient(Action<? super DockerClientParameters> action) {\n    action.execute(dockerClient);\n  }\n\n  public void outputPaths(Action<? super OutputPathsParameters> action) {\n    action.execute(outputPaths);\n  }\n\n  public void skaffold(Action<? super SkaffoldParameters> action) {\n    action.execute(skaffold);\n  }\n\n  public void pluginExtensions(Action<? super ExtensionParametersSpec> action) {\n    action.execute(extensionParametersSpec);\n  }\n\n  public void setAllowInsecureRegistries(boolean allowInsecureRegistries) {\n    this.allowInsecureRegistries.set(allowInsecureRegistries);\n  }\n\n  public void setContainerizingMode(String containerizingMode) {\n    this.containerizingMode.set(containerizingMode);\n  }\n\n  @Nested\n  @Optional\n  public BaseImageParameters getFrom() {\n    return from;\n  }\n\n  @Nested\n  @Optional\n  public TargetImageParameters getTo() {\n    return to;\n  }\n\n  @Nested\n  @Optional\n  public ContainerParameters getContainer() {\n    return container;\n  }\n\n  @Nested\n  @Optional\n  public ExtraDirectoriesParameters getExtraDirectories() {\n    return extraDirectories;\n  }\n\n  @Nested\n  @Optional\n  public DockerClientParameters getDockerClient() {\n    return dockerClient;\n  }\n\n  @Nested\n  @Optional\n  public OutputPathsParameters getOutputPaths() {\n    return outputPaths;\n  }\n\n  @Nested\n  @Optional\n  public SkaffoldParameters getSkaffold() {\n    return skaffold;\n  }\n\n  @Input\n  boolean getAllowInsecureRegistries() {\n    if (System.getProperty(PropertyNames.ALLOW_INSECURE_REGISTRIES) != null) {\n      return Boolean.getBoolean(PropertyNames.ALLOW_INSECURE_REGISTRIES);\n    }\n    return allowInsecureRegistries.get();\n  }\n\n  @Input\n  @Optional\n  public String getContainerizingMode() {\n    String property = System.getProperty(PropertyNames.CONTAINERIZING_MODE);\n    return property != null ? property : containerizingMode.get();\n  }\n\n  /**\n   * Returns the configurationName property while setting it to the value of the system property if\n   * present.\n   *\n   * @return The configurationName property\n   */\n  @Input\n  @Optional\n  public Property<String> getConfigurationName() {\n    String property = System.getProperty(PropertyNames.CONFIGURATION_NAME);\n    if (property != null && !property.equals(configurationName.get())) {\n      configurationName.set(property);\n    }\n    return configurationName;\n  }\n\n  @Nested\n  @Optional\n  public ListProperty<ExtensionParameters> getPluginExtensions() {\n    return pluginExtensions;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibPlugin.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.gradle.skaffold.CheckJibVersionTask;\nimport com.google.cloud.tools.jib.gradle.skaffold.FilesTaskV2;\nimport com.google.cloud.tools.jib.gradle.skaffold.InitTask;\nimport com.google.cloud.tools.jib.gradle.skaffold.SyncMapTask;\nimport com.google.cloud.tools.jib.plugins.common.VersionChecker;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableSet;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.Plugin;\nimport org.gradle.api.Project;\nimport org.gradle.api.Task;\nimport org.gradle.api.tasks.SourceSet;\nimport org.gradle.api.tasks.SourceSetContainer;\nimport org.gradle.api.tasks.TaskContainer;\nimport org.gradle.api.tasks.TaskProvider;\nimport org.gradle.api.tasks.bundling.Jar;\nimport org.gradle.util.GradleVersion;\n\npublic class JibPlugin implements Plugin<Project> {\n\n  @VisibleForTesting static final GradleVersion GRADLE_MIN_VERSION = GradleVersion.version(\"5.1\");\n\n  public static final String JIB_EXTENSION_NAME = \"jib\";\n  public static final String BUILD_IMAGE_TASK_NAME = \"jib\";\n  public static final String BUILD_TAR_TASK_NAME = \"jibBuildTar\";\n  public static final String BUILD_DOCKER_TASK_NAME = \"jibDockerBuild\";\n  public static final String SKAFFOLD_FILES_TASK_V2_NAME = \"_jibSkaffoldFilesV2\";\n  public static final String SKAFFOLD_INIT_TASK_NAME = \"_jibSkaffoldInit\";\n  public static final String SKAFFOLD_SYNC_MAP_TASK_NAME = \"_jibSkaffoldSyncMap\";\n  public static final String SKAFFOLD_CHECK_REQUIRED_VERSION_TASK_NAME =\n      \"_skaffoldFailIfJibOutOfDate\";\n\n  public static final String REQUIRED_VERSION_PROPERTY_NAME = \"jib.requiredVersion\";\n\n  private static void checkGradleVersion() {\n    if (GRADLE_MIN_VERSION.compareTo(GradleVersion.current()) > 0) {\n      throw new GradleException(\n          \"Detected \"\n              + GradleVersion.current()\n              + \", but jib requires \"\n              + GRADLE_MIN_VERSION\n              + \" or higher. You can upgrade by running 'gradle wrapper --gradle-version=\"\n              + GRADLE_MIN_VERSION.getVersion()\n              + \"'.\");\n    }\n  }\n\n  /** Check the Jib version matches the required version (if specified). */\n  private static void checkJibVersion() {\n    // todo: should retrieve from project properties?\n    String requiredVersion = System.getProperty(REQUIRED_VERSION_PROPERTY_NAME);\n    if (requiredVersion == null) {\n      return;\n    }\n    String actualVersion = GradleProjectProperties.TOOL_VERSION;\n    if (actualVersion == null) {\n      throw new GradleException(\"Could not determine Jib plugin version\");\n    }\n    VersionChecker<GradleVersion> checker = new VersionChecker<>(GradleVersion::version);\n    if (!checker.compatibleVersion(requiredVersion, actualVersion)) {\n      String failure =\n          String.format(\n              \"Jib plugin version is %s but is required to be %s\", actualVersion, requiredVersion);\n      throw new GradleException(failure);\n    }\n  }\n\n  @Override\n  public void apply(Project project) {\n    checkGradleVersion();\n    checkJibVersion();\n\n    JibExtension jibExtension =\n        project.getExtensions().create(JIB_EXTENSION_NAME, JibExtension.class, project);\n\n    TaskContainer tasks = project.getTasks();\n    TaskProvider<BuildImageTask> buildImageTask =\n        tasks.register(\n            BUILD_IMAGE_TASK_NAME,\n            BuildImageTask.class,\n            task -> {\n              task.setGroup(\"Jib\");\n              task.setDescription(\"Builds a container image to a registry.\");\n              task.setJibExtension(jibExtension);\n            });\n\n    TaskProvider<BuildDockerTask> buildDockerTask =\n        tasks.register(\n            BUILD_DOCKER_TASK_NAME,\n            BuildDockerTask.class,\n            task -> {\n              task.setGroup(\"Jib\");\n              task.setDescription(\"Builds a container image to a Docker daemon.\");\n              task.setJibExtension(jibExtension);\n            });\n\n    TaskProvider<BuildTarTask> buildTarTask =\n        tasks.register(\n            BUILD_TAR_TASK_NAME,\n            BuildTarTask.class,\n            task -> {\n              task.setGroup(\"Jib\");\n              task.setDescription(\"Builds a container image to a tarball.\");\n              task.setJibExtension(jibExtension);\n            });\n\n    tasks\n        .register(SKAFFOLD_FILES_TASK_V2_NAME, FilesTaskV2.class)\n        .configure(task -> task.setJibExtension(jibExtension));\n    tasks\n        .register(SKAFFOLD_INIT_TASK_NAME, InitTask.class)\n        .configure(task -> task.setJibExtension(jibExtension));\n    TaskProvider<SyncMapTask> syncMapTask =\n        tasks.register(\n            SKAFFOLD_SYNC_MAP_TASK_NAME,\n            SyncMapTask.class,\n            task -> task.setJibExtension(jibExtension));\n\n    // A check to catch older versions of Jib.  This can be removed once we are certain people\n    // are using Jib 1.3.1 or later.\n    tasks.register(SKAFFOLD_CHECK_REQUIRED_VERSION_TASK_NAME, CheckJibVersionTask.class);\n\n    project.afterEvaluate(\n        projectAfterEvaluation -> {\n          TaskProvider<Task> warTask = TaskCommon.getWarTaskProvider(projectAfterEvaluation);\n          TaskProvider<Task> bootWarTask =\n              TaskCommon.getBootWarTaskProvider(projectAfterEvaluation);\n          List<Object> jibDependencies = new ArrayList<>();\n          if (warTask != null || bootWarTask != null) {\n            // Have all tasks depend on the 'war' and/or 'bootWar' task.\n            if (warTask != null) {\n              jibDependencies.add(warTask);\n            }\n            if (bootWarTask != null) {\n              jibDependencies.add(bootWarTask);\n            }\n          } else if (\"packaged\".equals(jibExtension.getContainerizingMode())) {\n            // Have all tasks depend on the 'jar' task.\n            TaskProvider<Task> jarTask = projectAfterEvaluation.getTasks().named(\"jar\");\n            jibDependencies.add(jarTask);\n\n            if (projectAfterEvaluation.getPlugins().hasPlugin(\"org.springframework.boot\")) {\n              Task bootJarTask = projectAfterEvaluation.getTasks().getByName(\"bootJar\");\n\n              if (bootJarTask.getEnabled()) {\n                String bootJarPath = bootJarTask.getOutputs().getFiles().getAsPath();\n                String jarPath = jarTask.get().getOutputs().getFiles().getAsPath();\n                if (bootJarPath.equals(jarPath)) {\n                  if (!jarTask.get().getEnabled()) {\n                    ((Jar) jarTask.get()).getArchiveClassifier().set(\"original\");\n                  } else {\n                    throw new GradleException(\n                        \"Both 'bootJar' and 'jar' tasks are enabled, but they write their jar file \"\n                            + \"into the same location at \"\n                            + jarPath\n                            + \". Did you forget to set 'archiveClassifier' on either task?\");\n                  }\n                }\n              }\n              jarTask.get().setEnabled(true);\n            }\n          }\n\n          SourceSet mainSourceSet =\n              projectAfterEvaluation\n                  .getExtensions()\n                  .getByType(SourceSetContainer.class)\n                  .getByName(SourceSet.MAIN_SOURCE_SET_NAME);\n          jibDependencies.add(mainSourceSet.getRuntimeClasspath());\n          jibDependencies.add(\n              projectAfterEvaluation\n                  .getConfigurations()\n                  .getByName(jibExtension.getConfigurationName().get()));\n\n          Set<TaskProvider<?>> jibTaskProviders =\n              ImmutableSet.of(buildImageTask, buildDockerTask, buildTarTask, syncMapTask);\n          jibTaskProviders.forEach(\n              provider ->\n                  provider.configure(task -> jibDependencies.forEach(dep -> task.dependsOn(dep))));\n        });\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibTask.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport org.gradle.api.Task;\n\n/** A task with a {@link JibExtension}. */\npublic interface JibTask extends Task {\n\n  /**\n   * Sets the task's {@link JibExtension}.\n   *\n   * @param jibExtension the {@link JibExtension}\n   * @return this\n   */\n  Task setJibExtension(JibExtension jibExtension);\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/OutputPathsParameters.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport javax.inject.Inject;\nimport org.gradle.api.Project;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\n\n/** Object that configures where Jib should create its build output files. */\npublic class OutputPathsParameters {\n\n  private final Project project;\n\n  private Path digest;\n  private Path tar;\n  private Path imageId;\n  private Path imageJson;\n\n  @Inject\n  public OutputPathsParameters(Project project) {\n    this.project = project;\n    digest = project.getBuildDir().toPath().resolve(\"jib-image.digest\");\n    imageId = project.getBuildDir().toPath().resolve(\"jib-image.id\");\n    imageJson = project.getBuildDir().toPath().resolve(\"jib-image.json\");\n    tar = project.getBuildDir().toPath().resolve(\"jib-image.tar\");\n  }\n\n  @Input\n  public String getDigest() {\n    return getRelativeToProjectRoot(digest, PropertyNames.OUTPUT_PATHS_DIGEST).toString();\n  }\n\n  @Internal\n  Path getDigestPath() {\n    return getRelativeToProjectRoot(digest, PropertyNames.OUTPUT_PATHS_DIGEST);\n  }\n\n  public void setDigest(String digest) {\n    this.digest = Paths.get(digest);\n  }\n\n  @Input\n  public String getImageId() {\n    return getRelativeToProjectRoot(imageId, PropertyNames.OUTPUT_PATHS_IMAGE_ID).toString();\n  }\n\n  @Internal\n  Path getImageIdPath() {\n    return getRelativeToProjectRoot(imageId, PropertyNames.OUTPUT_PATHS_IMAGE_ID);\n  }\n\n  public void setImageId(String id) {\n    this.imageId = Paths.get(id);\n  }\n\n  @Input\n  public String getImageJson() {\n    return getRelativeToProjectRoot(imageJson, PropertyNames.OUTPUT_PATHS_IMAGE_JSON).toString();\n  }\n\n  @Internal\n  Path getImageJsonPath() {\n    return getRelativeToProjectRoot(imageJson, PropertyNames.OUTPUT_PATHS_IMAGE_JSON);\n  }\n\n  public void setImageJson(String imageJson) {\n    this.imageJson = Paths.get(imageJson);\n  }\n\n  @Input\n  public String getTar() {\n    return getRelativeToProjectRoot(tar, PropertyNames.OUTPUT_PATHS_TAR).toString();\n  }\n\n  @Internal\n  Path getTarPath() {\n    return getRelativeToProjectRoot(tar, PropertyNames.OUTPUT_PATHS_TAR);\n  }\n\n  public void setTar(String tar) {\n    this.tar = Paths.get(tar);\n  }\n\n  private Path getRelativeToProjectRoot(Path configuration, String propertyName) {\n    String property = System.getProperty(propertyName);\n    Path path = property != null ? Paths.get(property) : configuration;\n    return path.isAbsolute() ? path : project.getProjectDir().toPath().resolve(path);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/PlatformParameters.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Internal;\n\n/** Configuration of a platform. */\npublic class PlatformParameters implements PlatformConfiguration {\n\n  static PlatformParameters of(String osArchitecture) {\n    Matcher matcher = Pattern.compile(\"([^/ ]+)/([^/ ]+)\").matcher(osArchitecture);\n    if (!matcher.matches()) {\n      throw new IllegalArgumentException(\"Platform must be of form os/architecture.\");\n    }\n    PlatformParameters platformParameters = new PlatformParameters();\n    platformParameters.os = matcher.group(1);\n    platformParameters.architecture = matcher.group(2);\n    return platformParameters;\n  }\n\n  @Nullable private String os;\n  @Nullable private String architecture;\n\n  @Input\n  @Nullable\n  public String getOs() {\n    return os;\n  }\n\n  @Internal\n  @Override\n  public Optional<String> getOsName() {\n    return Optional.ofNullable(os);\n  }\n\n  public void setOs(String os) {\n    this.os = os;\n  }\n\n  @Input\n  @Nullable\n  public String getArchitecture() {\n    return architecture;\n  }\n\n  @Internal\n  @Override\n  public Optional<String> getArchitectureName() {\n    return Optional.ofNullable(architecture);\n  }\n\n  public void setArchitecture(String architecture) {\n    this.architecture = architecture;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (this == other) {\n      return true;\n    }\n    if (!(other instanceof PlatformParameters)) {\n      return false;\n    }\n    PlatformParameters otherPlatform = (PlatformParameters) other;\n    return Objects.equals(architecture, otherPlatform.getArchitecture())\n        && Objects.equals(os, otherPlatform.getOs());\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(architecture, os);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/PlatformParametersSpec.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.ListProperty;\n\n/** Allows to add {@link PlatformParameters} objects to the list property of the same type. */\npublic class PlatformParametersSpec {\n\n  private final ObjectFactory objectFactory;\n  private final ListProperty<PlatformParameters> platforms;\n\n  @Inject\n  public PlatformParametersSpec(\n      ObjectFactory objectFactory, ListProperty<PlatformParameters> platforms) {\n    this.objectFactory = objectFactory;\n    this.platforms = platforms;\n  }\n\n  /**\n   * Adds a new platform configuration to the platforms list.\n   *\n   * @param action closure representing a platform configuration\n   */\n  public void platform(Action<? super PlatformParameters> action) {\n    PlatformParameters platform = objectFactory.newInstance(PlatformParameters.class);\n    action.execute(platform);\n    platforms.add(platform);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/TargetImageParameters.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.common.collect.ImmutableSet;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.Nullable;\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.provider.SetProperty;\nimport org.gradle.api.tasks.Input;\nimport org.gradle.api.tasks.Nested;\nimport org.gradle.api.tasks.Optional;\n\n/** Object in {@link JibExtension} that configures the target image. */\npublic class TargetImageParameters {\n\n  private final AuthParameters auth;\n  private final Property<String> image;\n  private final SetProperty<String> tags;\n  private final CredHelperParameters credHelper;\n\n  @Inject\n  public TargetImageParameters(ObjectFactory objectFactory) {\n    auth = objectFactory.newInstance(AuthParameters.class, \"to.auth\");\n    image = objectFactory.property(String.class);\n    tags = objectFactory.setProperty(String.class).empty();\n    credHelper =\n        objectFactory.newInstance(CredHelperParameters.class, PropertyNames.TO_CRED_HELPER);\n  }\n\n  @Input\n  @Nullable\n  @Optional\n  public String getImage() {\n    if (System.getProperty(PropertyNames.TO_IMAGE) != null) {\n      return System.getProperty(PropertyNames.TO_IMAGE);\n    }\n    return image.getOrNull();\n  }\n\n  public void setImage(String image) {\n    this.image.set(image);\n  }\n\n  public void setImage(Provider<String> image) {\n    this.image.set(image);\n  }\n\n  @Input\n  @Optional\n  public Set<String> getTags() {\n    String property = System.getProperty(PropertyNames.TO_TAGS);\n    Set<String> tagsValue;\n    if (property != null) {\n      tagsValue = ImmutableSet.copyOf(ConfigurationPropertyValidator.parseListProperty(property));\n    } else {\n      try {\n        tagsValue = tags.get();\n      } catch (NullPointerException ex) {\n        throw new IllegalArgumentException(\"jib.to.tags contains null tag\");\n      }\n    }\n    if (tagsValue.stream().anyMatch(str -> str.isEmpty())) {\n      throw new IllegalArgumentException(\"jib.to.tags contains empty tag\");\n    }\n    return tagsValue;\n  }\n\n  public void setTags(List<String> tags) {\n    this.tags.set(tags);\n  }\n\n  public void setTags(Set<String> tags) {\n    this.tags.set(tags);\n  }\n\n  public void setTags(Provider<Set<String>> tags) {\n    this.tags.set(tags);\n  }\n\n  @Nested\n  @Optional\n  public CredHelperParameters getCredHelper() {\n    return credHelper;\n  }\n\n  public void setCredHelper(String helper) {\n    this.credHelper.setHelper(helper);\n  }\n\n  public void credHelper(Action<? super CredHelperParameters> action) {\n    action.execute(credHelper);\n  }\n\n  @Nested\n  @Optional\n  public AuthParameters getAuth() {\n    // System properties are handled in ConfigurationPropertyValidator\n    return auth;\n  }\n\n  public void auth(Action<? super AuthParameters> action) {\n    action.execute(auth);\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/TaskCommon.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.api.client.http.HttpTransport;\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.plugins.common.ProjectProperties;\nimport com.google.cloud.tools.jib.plugins.common.UpdateChecker;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.common.util.concurrent.Futures;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.logging.Level;\nimport javax.annotation.Nullable;\nimport org.gradle.api.Project;\nimport org.gradle.api.Task;\nimport org.gradle.api.UnknownTaskException;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.plugins.WarPlugin;\nimport org.gradle.api.tasks.TaskProvider;\nimport org.gradle.internal.logging.events.OutputEventListener;\nimport org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext;\nimport org.slf4j.LoggerFactory;\n\n/** Collection of common methods to share between Gradle tasks. */\nclass TaskCommon {\n\n  public static final String VERSION_URL = \"https://storage.googleapis.com/jib-versions/jib-gradle\";\n\n  static Future<Optional<String>> newUpdateChecker(\n      ProjectProperties projectProperties, GlobalConfig globalConfig, Logger logger) {\n    if (projectProperties.isOffline()\n        || !logger.isLifecycleEnabled()\n        || globalConfig.isDisableUpdateCheck()) {\n      return Futures.immediateFuture(Optional.empty());\n    }\n    ExecutorService executorService = Executors.newSingleThreadExecutor();\n    try {\n      return UpdateChecker.checkForUpdate(\n          executorService,\n          VERSION_URL,\n          projectProperties.getToolName(),\n          projectProperties.getToolVersion(),\n          projectProperties::log);\n    } finally {\n      executorService.shutdown();\n    }\n  }\n\n  static void finishUpdateChecker(\n      ProjectProperties projectProperties, Future<Optional<String>> updateCheckFuture) {\n    UpdateChecker.finishUpdateCheck(updateCheckFuture)\n        .ifPresent(\n            latestVersion -> {\n              String changelogUrl =\n                  ProjectInfo.GITHUB_URL + \"/blob/master/jib-gradle-plugin/CHANGELOG.md\";\n              String privacyUrl = ProjectInfo.GITHUB_URL + \"/blob/master/docs/privacy.md\";\n              String message =\n                  String.format(\n                      \"\\n\\u001B[33mA new version of %s (%s) is available (currently using %s). Update your\"\n                          + \" build configuration to use the latest features and fixes!\\n%s\\u001B[0m\\n\\nPlease see\"\n                          + \" %s for info on disabling this update check.\\n\",\n                      projectProperties.getToolName(),\n                      latestVersion,\n                      projectProperties.getToolVersion(),\n                      changelogUrl,\n                      privacyUrl);\n              projectProperties.log(LogEvent.lifecycle(message));\n            });\n  }\n\n  @Nullable\n  static TaskProvider<Task> getWarTaskProvider(Project project) {\n    if (project.getPlugins().hasPlugin(WarPlugin.class)) {\n      return project.getTasks().named(WarPlugin.WAR_TASK_NAME);\n    }\n    return null;\n  }\n\n  @Nullable\n  static TaskProvider<Task> getBootWarTaskProvider(Project project) {\n    if (project.getPlugins().hasPlugin(\"org.springframework.boot\")) {\n      try {\n        return project.getTasks().named(\"bootWar\");\n      } catch (UnknownTaskException ignored) { // fall through\n      }\n    }\n    return null;\n  }\n\n  /** Disables annoying Apache HTTP client logging. */\n  static void disableHttpLogging() {\n    // Disables Apache HTTP client logging.\n    OutputEventListenerBackedLoggerContext context =\n        (OutputEventListenerBackedLoggerContext) LoggerFactory.getILoggerFactory();\n    OutputEventListener defaultOutputEventListener = context.getOutputEventListener();\n    context.setOutputEventListener(\n        event -> {\n          org.gradle.internal.logging.events.LogEvent logEvent =\n              (org.gradle.internal.logging.events.LogEvent) event;\n          if (!logEvent.getCategory().contains(\"org.apache\")) {\n            defaultOutputEventListener.onOutput(event);\n          }\n        });\n\n    // Disable Google HTTP Client network logging only when 'java.util.logging.config.file' system\n    // property is undefined: https://github.com/GoogleContainerTools/jib/issues/2356\n    if (System.getProperty(\"java.util.logging.config.file\") == null) {\n      // Disables Google HTTP client logging.\n      java.util.logging.Logger.getLogger(HttpTransport.class.getName()).setLevel(Level.OFF);\n    }\n  }\n\n  /**\n   * Converts a {@code String->String} file-path-to-file-permissions map to an equivalent {@code\n   * String->FilePermission} map.\n   *\n   * @param stringMap the map to convert (example entry: {@code \"/path/on/container\" -> \"755\"})\n   * @return the converted map\n   */\n  static Map<String, FilePermissions> convertPermissionsMap(Map<String, String> stringMap) {\n    // Order is important, so use a LinkedHashMap\n    Map<String, FilePermissions> permissionsMap = new LinkedHashMap<>();\n    for (Map.Entry<String, String> entry : stringMap.entrySet()) {\n      permissionsMap.put(entry.getKey(), FilePermissions.fromOctalString(entry.getValue()));\n    }\n    return permissionsMap;\n  }\n\n  private TaskCommon() {}\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/CheckJibVersionTask.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.cloud.tools.jib.gradle.JibPlugin;\nimport com.google.common.base.Strings;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.tasks.TaskAction;\n\n/**\n * This internal Skaffold-related goal checks that the Jib plugin version is within some specified\n * range. It is only required so that older versions of Jib (prior to the introduction of the {@code\n * jib.requiredVersion} property) will error in such a way that it indicates the jib version is out\n * of date. This goal can be removed once there are no users of Jib prior to 1.4.0.\n *\n * <p>Expected use: {@code ./gradlew _skaffoldFailIfJibOutOfDate -Djib.requiredVersion='[1.4,2)'\n * jibDockerBuild --image=xxx}\n */\npublic class CheckJibVersionTask extends DefaultTask {\n\n  /** Task Action, check if jib and skaffold versions are compatible. */\n  @TaskAction\n  public void checkVersion() {\n    if (Strings.isNullOrEmpty(System.getProperty(JibPlugin.REQUIRED_VERSION_PROPERTY_NAME))) {\n      throw new GradleException(\n          JibPlugin.SKAFFOLD_CHECK_REQUIRED_VERSION_TASK_NAME\n              + \" requires \"\n              + JibPlugin.REQUIRED_VERSION_PROPERTY_NAME\n              + \" to be set\");\n    }\n    // no-op as Jib version compatibility is actually checked in JibPlugin\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.cloud.tools.jib.gradle.ExtraDirectoryParameters;\nimport com.google.cloud.tools.jib.gradle.JibExtension;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldFilesOutput;\nimport com.google.common.base.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.Project;\nimport org.gradle.api.artifacts.Configuration;\nimport org.gradle.api.artifacts.Dependency;\nimport org.gradle.api.artifacts.ProjectDependency;\nimport org.gradle.api.artifacts.PublishArtifact;\nimport org.gradle.api.initialization.Settings;\nimport org.gradle.api.tasks.SourceSet;\nimport org.gradle.api.tasks.SourceSetContainer;\nimport org.gradle.api.tasks.TaskAction;\n\n/**\n * Prints out changing source dependencies on a project.\n *\n * <p>Expected use: {@code ./gradlew _jibSkaffoldFilesV2 -q} or {@code ./gradlew\n * :<subproject>:_jibSkaffoldFilesV2 -q}\n */\npublic class FilesTaskV2 extends DefaultTask {\n\n  private final SkaffoldFilesOutput skaffoldFilesOutput = new SkaffoldFilesOutput();\n\n  @Nullable private JibExtension jibExtension;\n\n  public FilesTaskV2 setJibExtension(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n    return this;\n  }\n\n  /**\n   * Task Action, print files.\n   *\n   * @throws IOException if an error occurs generating the json string\n   */\n  @TaskAction\n  public void listFiles() throws IOException {\n    Preconditions.checkNotNull(jibExtension);\n    Project project = getProject();\n\n    // If this is not the root project, add the root project's build.gradle and settings.gradle\n    if (project != project.getRootProject()) {\n      addGradleFiles(project.getRootProject());\n    }\n\n    addProjectFiles(project);\n\n    // Add extra layer\n    List<Path> extraDirectories =\n        jibExtension.getExtraDirectories().getPaths().stream()\n            .map(ExtraDirectoryParameters::getFrom)\n            .collect(Collectors.toList());\n    extraDirectories.stream().filter(Files::exists).forEach(skaffoldFilesOutput::addInput);\n\n    // Find project dependencies\n    Set<ProjectDependency> projectDependencies = findProjectDependencies(project);\n\n    Set<File> projectDependencyJars = new HashSet<>();\n    for (ProjectDependency projectDependency : projectDependencies) {\n      Project dependentProject = getDependentProject(projectDependency);\n      addProjectFiles(dependentProject);\n\n      // Keep track of project dependency jars for filtering out later\n      String configurationName = projectDependency.getTargetConfiguration();\n      if (configurationName == null) {\n        configurationName = \"default\";\n      }\n      for (Configuration targetConfiguration :\n          dependentProject.getConfigurations().getByName(configurationName).getHierarchy()) {\n        for (PublishArtifact artifact : targetConfiguration.getArtifacts()) {\n          projectDependencyJars.add(artifact.getFile());\n        }\n      }\n    }\n\n    // Add SNAPSHOT, non-project dependency jars\n    for (File file :\n        project.getConfigurations().getByName(jibExtension.getConfigurationName().get())) {\n      if (!projectDependencyJars.contains(file) && file.toString().contains(\"SNAPSHOT\")) {\n        skaffoldFilesOutput.addInput(file.toPath());\n        projectDependencyJars.add(file); // Add to set to avoid printing the same files twice\n      }\n    }\n\n    // Configure other files from config\n    SkaffoldWatchParameters watch = jibExtension.getSkaffold().getWatch();\n    watch.getBuildIncludes().forEach(skaffoldFilesOutput::addBuild);\n    watch.getIncludes().forEach(skaffoldFilesOutput::addInput);\n    // we don't do any special pre-processing for ignore (input and ignore can overlap with exact\n    // matches)\n    watch.getExcludes().forEach(skaffoldFilesOutput::addIgnore);\n\n    // Print files\n    System.out.println();\n    System.out.println(\"BEGIN JIB JSON\");\n    System.out.println(skaffoldFilesOutput.getJsonString());\n  }\n\n  /**\n   * Adds the locations of a project's build.gradle, settings.gradle, and gradle.properties.\n   *\n   * @param project the project\n   */\n  private void addGradleFiles(Project project) {\n    Path projectPath = project.getProjectDir().toPath();\n\n    // Add build.gradle\n    skaffoldFilesOutput.addBuild(project.getBuildFile().toPath());\n\n    // Add settings.gradle\n    addSettingsFile(project, projectPath);\n\n    // Add gradle.properties\n    if (Files.exists(projectPath.resolve(\"gradle.properties\"))) {\n      skaffoldFilesOutput.addBuild(projectPath.resolve(\"gradle.properties\"));\n    }\n  }\n\n  /**\n   * Adds the settings.gradle file for a project.\n   *\n   * <p>Uses reflection to call getSettingsFile() for compatibility with both Gradle 6 and 9\n   * (getSettingsFile() was removed in Gradle 9).\n   *\n   * @param project the project\n   * @param projectPath the project directory path\n   */\n  private void addSettingsFile(Project project, Path projectPath) {\n    boolean settingsFileAdded = false;\n    try {\n      Object startParameter = project.getGradle().getStartParameter();\n      java.lang.reflect.Method getSettingsFileMethod =\n          startParameter.getClass().getMethod(\"getSettingsFile\");\n      File settingsFile = (File) getSettingsFileMethod.invoke(startParameter);\n      if (settingsFile != null) {\n        skaffoldFilesOutput.addBuild(settingsFile.toPath());\n        settingsFileAdded = true;\n      }\n    } catch (ReflectiveOperationException e) {\n      // Fall through to default settings file check\n    }\n\n    // Fall back to default settings file location if not already added\n    if (!settingsFileAdded && Files.exists(projectPath.resolve(Settings.DEFAULT_SETTINGS_FILE))) {\n      skaffoldFilesOutput.addBuild(projectPath.resolve(Settings.DEFAULT_SETTINGS_FILE));\n    }\n  }\n\n  /**\n   * Prints build files, sources, and resources associated with a project.\n   *\n   * @param project the project\n   */\n  private void addProjectFiles(Project project) {\n    // Add build config, settings, etc.\n    addGradleFiles(project);\n\n    // Add sources + resources\n    SourceSetContainer sourceSetContainer =\n        project.getExtensions().findByType(SourceSetContainer.class);\n    if (sourceSetContainer != null) {\n      SourceSet mainSourceSet = sourceSetContainer.findByName(SourceSet.MAIN_SOURCE_SET_NAME);\n      if (mainSourceSet != null) {\n        mainSourceSet\n            .getAllSource()\n            .getSourceDirectories()\n            .forEach(\n                sourceDirectory -> {\n                  if (sourceDirectory.exists()) {\n                    skaffoldFilesOutput.addInput(sourceDirectory.toPath());\n                  }\n                });\n      }\n    }\n  }\n\n  /**\n   * Collects a project's project dependencies, including all transitive project dependencies.\n   *\n   * @param project the project to find the project dependencies for\n   * @return the set of project dependencies\n   */\n  private Set<ProjectDependency> findProjectDependencies(Project project) {\n    Preconditions.checkNotNull(jibExtension);\n\n    Set<ProjectDependency> projectDependencies = new HashSet<>();\n    Deque<Project> projects = new ArrayDeque<>();\n    projects.push(project);\n\n    String configurationName = jibExtension.getConfigurationName().get();\n\n    while (!projects.isEmpty()) {\n      Project currentProject = projects.pop();\n\n      // Search through all dependencies\n      Configuration runtimeClasspath =\n          currentProject.getConfigurations().findByName(configurationName);\n      if (runtimeClasspath != null) {\n        for (Configuration configuration : runtimeClasspath.getHierarchy()) {\n          for (Dependency dependency : configuration.getDependencies()) {\n            if (dependency instanceof ProjectDependency) {\n              // If this is a project dependency, save it\n              ProjectDependency projectDependency = (ProjectDependency) dependency;\n              if (!projectDependencies.contains(projectDependency)) {\n                projects.push(getDependentProject(projectDependency));\n                projectDependencies.add(projectDependency);\n              }\n            }\n          }\n        }\n      }\n    }\n    return projectDependencies;\n  }\n\n  /**\n   * Resolves a {@link ProjectDependency} to its corresponding {@link Project} instance.\n   *\n   * <p>Uses reflection to handle both Gradle 6 (getDependencyProject()) and Gradle 9+ (getPath()).\n   *\n   * @param projectDependency the project dependency to resolve\n   * @return the resolved project\n   * @throws RuntimeException if the dependent project could not be resolved\n   */\n  private Project getDependentProject(ProjectDependency projectDependency) {\n    // Try getDependencyProject() first (Gradle 6-8)\n    try {\n      java.lang.reflect.Method getDependencyProjectMethod =\n          projectDependency.getClass().getMethod(\"getDependencyProject\");\n      return (Project) getDependencyProjectMethod.invoke(projectDependency);\n    } catch (NoSuchMethodException e) {\n      // Fall through to getPath() approach (Gradle 9+)\n    } catch (ReflectiveOperationException e) {\n      throw new RuntimeException(\n          \"Failed to resolve dependent project from \" + projectDependency, e);\n    }\n\n    // Try getPath() approach (Gradle 9+)\n    try {\n      java.lang.reflect.Method getPathMethod = projectDependency.getClass().getMethod(\"getPath\");\n      String path = (String) getPathMethod.invoke(projectDependency);\n      return getProject().project(path);\n    } catch (ReflectiveOperationException e) {\n      throw new RuntimeException(\n          \"Failed to resolve dependent project from \" + projectDependency, e);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/InitTask.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.cloud.tools.jib.gradle.JibExtension;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldInitOutput;\nimport com.google.common.base.Preconditions;\nimport java.io.IOException;\nimport javax.annotation.Nullable;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.Project;\nimport org.gradle.api.tasks.TaskAction;\n\n/**\n * Prints out to.image configuration and project name, used for Jib project detection in Skaffold.\n *\n * <p>Expected use: {@code ./gradlew _jibSkaffoldInit -q}\n */\npublic class InitTask extends DefaultTask {\n\n  @Nullable private JibExtension jibExtension;\n\n  public InitTask setJibExtension(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n    return this;\n  }\n\n  /**\n   * Task Action, lists modules and targets.\n   *\n   * @throws IOException if an error occurs generating the json string\n   */\n  @TaskAction\n  public void listModulesAndTargets() throws IOException {\n    Project project = getProject();\n    // Ignore parent projects\n    if (!project.getSubprojects().isEmpty()) {\n      return;\n    }\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput();\n    skaffoldInitOutput.setImage(Preconditions.checkNotNull(jibExtension).getTo().getImage());\n    if (!project.equals(project.getRootProject())) {\n      skaffoldInitOutput.setProject(project.getName());\n    }\n    System.out.println();\n    System.out.println(\"BEGIN JIB JSON\");\n    System.out.println(skaffoldInitOutput.getJsonString());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/SkaffoldParameters.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.common.base.Preconditions;\nimport javax.inject.Inject;\nimport org.gradle.api.Action;\nimport org.gradle.api.Project;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.tasks.Nested;\n\n/** Skaffold specific JibExtension parameters. */\npublic class SkaffoldParameters {\n\n  private final SkaffoldWatchParameters watch;\n  private final SkaffoldSyncParameters sync;\n\n  @Inject\n  public SkaffoldParameters(Project project) {\n    ObjectFactory objectFactory = project.getObjects();\n\n    watch = objectFactory.newInstance(SkaffoldWatchParameters.class, project);\n    sync = objectFactory.newInstance(SkaffoldSyncParameters.class, project);\n\n    Preconditions.checkNotNull(watch);\n  }\n\n  public void watch(Action<? super SkaffoldWatchParameters> action) {\n    action.execute(watch);\n  }\n\n  public void sync(Action<? super SkaffoldSyncParameters> action) {\n    action.execute(sync);\n  }\n\n  @Nested\n  public SkaffoldWatchParameters getWatch() {\n    return watch;\n  }\n\n  @Nested\n  public SkaffoldSyncParameters getSync() {\n    return sync;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/SkaffoldSyncParameters.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.inject.Inject;\nimport org.gradle.api.Project;\nimport org.gradle.api.tasks.Internal;\n\n/** Skaffold specific JibExtension parameters for configuring files to sync. */\npublic class SkaffoldSyncParameters {\n  private final Project project;\n\n  private Set<Path> excludes = Collections.emptySet();\n\n  @Inject\n  public SkaffoldSyncParameters(Project project) {\n    this.project = project;\n  }\n\n  /**\n   * Get the excludes directive for sync functionality in skaffold.\n   *\n   * @return a set of absolute paths\n   */\n  @Internal\n  public Set<Path> getExcludes() {\n    return excludes;\n  }\n\n  /**\n   * Sets excludes. {@code excludes} can be any suitable object describing file paths convertible by\n   * {@link Project#files} (such as {@link File}, {@code List<File>}, or {@code List<String>}).\n   *\n   * @param paths paths to set on excludes\n   */\n  public void setExcludes(Object paths) {\n    this.excludes =\n        project.files(paths).getFiles().stream()\n            .map(File::toPath)\n            .map(Path::toAbsolutePath)\n            .collect(Collectors.toSet());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/SkaffoldWatchParameters.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.inject.Inject;\nimport org.gradle.api.Project;\nimport org.gradle.api.tasks.Internal;\n\n/** Skaffold specific JibExtension parameters for configuring files to watch. */\npublic class SkaffoldWatchParameters {\n\n  private final Project project;\n\n  private Set<Path> buildIncludes = Collections.emptySet();\n  private Set<Path> includes = Collections.emptySet();\n  private Set<Path> excludes = Collections.emptySet();\n\n  @Inject\n  public SkaffoldWatchParameters(Project project) {\n    this.project = project;\n  }\n\n  /**\n   * A set of absolute paths to include with skaffold watching.\n   *\n   * @return a set of absolute paths\n   */\n  @Internal\n  public Set<Path> getBuildIncludes() {\n    return buildIncludes;\n  }\n\n  /**\n   * Sets includes. {@code includes} can be any suitable object describing file paths convertible by\n   * {@link Project#files} (such as {@link File}, {@code List<File>}, or {@code List<String>}).\n   *\n   * @param paths paths to set on includes\n   */\n  public void setBuildIncludes(Object paths) {\n    this.buildIncludes =\n        project.files(paths).getFiles().stream()\n            .map(File::toPath)\n            .map(Path::toAbsolutePath)\n            .collect(Collectors.toSet());\n  }\n\n  /**\n   * A set of absolute paths to include with skaffold watching.\n   *\n   * @return a set of absolute paths\n   */\n  @Internal\n  public Set<Path> getIncludes() {\n    return includes;\n  }\n\n  /**\n   * Sets includes. {@code includes} can be any suitable object describing file paths convertible by\n   * {@link Project#files} (such as {@link File}, {@code List<File>}, or {@code List<String>}).\n   *\n   * @param paths paths to set on includes\n   */\n  public void setIncludes(Object paths) {\n    this.includes =\n        project.files(paths).getFiles().stream()\n            .map(File::toPath)\n            .map(Path::toAbsolutePath)\n            .collect(Collectors.toSet());\n  }\n\n  /**\n   * A set of absolute paths to exclude from skaffold watching.\n   *\n   * @return a set of absolute paths\n   */\n  @Internal\n  public Set<Path> getExcludes() {\n    // Gradle warns about @Input annotations on File objects, so we have to expose a getter for a\n    // String to make them go away.\n    return excludes;\n  }\n\n  /**\n   * Sets excludes. {@code excludes} can be any suitable object describing file paths convertible by\n   * {@link Project#files} (such as {@link File}, {@code List<File>}, or {@code List<String>}).\n   *\n   * @param paths paths to set on excludes\n   */\n  public void setExcludes(Object paths) {\n    this.excludes =\n        project.files(paths).getFiles().stream()\n            .map(File::toPath)\n            .map(Path::toAbsolutePath)\n            .collect(Collectors.toSet());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/SyncMapTask.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.gradle.GradleProjectProperties;\nimport com.google.cloud.tools.jib.gradle.GradleRawConfiguration;\nimport com.google.cloud.tools.jib.gradle.JibExtension;\nimport com.google.cloud.tools.jib.plugins.common.ContainerizingMode;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.common.base.Preconditions;\nimport javax.annotation.Nullable;\nimport org.gradle.api.DefaultTask;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.tasks.TaskAction;\n\n/**\n * Prints out a map of local files to their location on the container.\n *\n * <p>Expected use: {@code ./gradlew _jibSkaffoldSyncMap -q} or {@code ./gradlew\n * :<subproject>:_jibSkaffoldSyncMap -q}\n */\npublic class SyncMapTask extends DefaultTask {\n\n  @Nullable private JibExtension jibExtension;\n\n  public SyncMapTask setJibExtension(JibExtension jibExtension) {\n    this.jibExtension = jibExtension;\n    return this;\n  }\n\n  /** Task Action, lists files and container targets. */\n  @TaskAction\n  public void listFilesAndTargets() {\n    Preconditions.checkNotNull(jibExtension);\n\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      GradleProjectProperties projectProperties =\n          GradleProjectProperties.getForProject(\n              getProject(),\n              getLogger(),\n              tempDirectoryProvider,\n              jibExtension.getConfigurationName().get());\n\n      GradleRawConfiguration configuration = new GradleRawConfiguration(jibExtension);\n\n      // TODO: move these shared checks with SyncMapMojo into plugins-common\n      if (projectProperties.isWarProject()) {\n        throw new GradleException(\n            \"Skaffold sync is currently only available for 'jar' style Jib projects, but the project \"\n                + getProject().getName()\n                + \" is configured to generate a 'war'\");\n      }\n      try {\n        if (!ContainerizingMode.EXPLODED.equals(\n            ContainerizingMode.from(jibExtension.getContainerizingMode()))) {\n          throw new GradleException(\n              \"Skaffold sync is currently only available for Jib projects in 'exploded' containerizing mode, but the containerizing mode of \"\n                  + getProject().getName()\n                  + \" is '\"\n                  + jibExtension.getContainerizingMode()\n                  + \"'\");\n        }\n      } catch (InvalidContainerizingModeException ex) {\n        throw new GradleException(\"Invalid containerizing mode\", ex);\n      }\n\n      try {\n        String syncMapJson =\n            PluginConfigurationProcessor.getSkaffoldSyncMap(\n                configuration,\n                projectProperties,\n                jibExtension.getSkaffold().getSync().getExcludes());\n\n        System.out.println();\n        System.out.println(\"BEGIN JIB JSON: SYNCMAP/1\");\n        System.out.println(syncMapJson);\n\n      } catch (Exception ex) {\n        throw new GradleException(\"Failed to generate a Jib file map for sync with Skaffold\", ex);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesExtensionTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.gradle.extension.GradleData;\nimport com.google.cloud.tools.jib.gradle.extension.JibGradlePluginExtension;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger.LogLevel;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.collect.ImmutableMap;\nimport java.io.FileNotFoundException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.gradle.api.Action;\nimport org.gradle.api.Project;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.logging.configuration.ConsoleOutput;\nimport org.gradle.api.model.ObjectFactory;\nimport org.gradle.api.plugins.JavaPlugin;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Answers;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Plugin extension test for {@link GradleProjectProperties}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class GradleProjectPropertiesExtensionTest {\n\n  // Interface to conveniently provide the main extension body using lambda.\n  @FunctionalInterface\n  private interface MainExtensionBody<T> {\n\n    ContainerBuildPlan extendContainerBuildPlan(\n        ContainerBuildPlan buildPlan,\n        Map<String, String> properties,\n        Optional<T> extraConfig,\n        GradleData gradleData,\n        ExtensionLogger logger)\n        throws JibPluginExtensionException;\n  }\n\n  private static class BaseExtension<T> implements JibGradlePluginExtension<T> {\n\n    private final MainExtensionBody<T> extensionBody;\n    private final Class<T> extraConfigType;\n\n    private BaseExtension(MainExtensionBody<T> extensionBody, Class<T> extraConfigType) {\n      this.extensionBody = extensionBody;\n      this.extraConfigType = extraConfigType;\n    }\n\n    @Override\n    public Optional<Class<T>> getExtraConfigType() {\n      return Optional.ofNullable(extraConfigType);\n    }\n\n    @Override\n    public ContainerBuildPlan extendContainerBuildPlan(\n        ContainerBuildPlan buildPlan,\n        Map<String, String> properties,\n        Optional<T> extraConfig,\n        GradleData gradleData,\n        ExtensionLogger logger)\n        throws JibPluginExtensionException {\n      return extensionBody.extendContainerBuildPlan(\n          buildPlan, properties, extraConfig, gradleData, logger);\n    }\n  }\n\n  private static class FooExtension extends BaseExtension<ExtensionDefinedFooConfig> {\n\n    private FooExtension(MainExtensionBody<ExtensionDefinedFooConfig> extensionBody) {\n      super(extensionBody, ExtensionDefinedFooConfig.class);\n    }\n  }\n\n  private static class BarExtension extends BaseExtension<ExtensionDefinedBarConfig> {\n\n    private BarExtension(MainExtensionBody<ExtensionDefinedBarConfig> extensionBody) {\n      super(extensionBody, ExtensionDefinedBarConfig.class);\n    }\n  }\n\n  private static class BaseExtensionConfig<T> implements ExtensionConfiguration {\n\n    private final String extensionClass;\n    private final Map<String, String> properties;\n    private final Action<T> extraConfig;\n\n    private BaseExtensionConfig(\n        String extensionClass, Map<String, String> properties, Action<T> extraConfig) {\n      this.extensionClass = extensionClass;\n      this.properties = properties;\n      this.extraConfig = extraConfig;\n    }\n\n    @Override\n    public Map<String, String> getProperties() {\n      return properties;\n    }\n\n    @Override\n    public String getExtensionClass() {\n      return extensionClass;\n    }\n\n    @Override\n    public Optional<Object> getExtraConfiguration() {\n      return Optional.ofNullable(extraConfig);\n    }\n  }\n\n  private static class FooExtensionConfig extends BaseExtensionConfig<ExtensionDefinedFooConfig> {\n\n    private FooExtensionConfig() {\n      super(FooExtension.class.getName(), Collections.emptyMap(), null);\n    }\n\n    private FooExtensionConfig(Map<String, String> properties) {\n      super(FooExtension.class.getName(), properties, null);\n    }\n\n    private FooExtensionConfig(ExtensionDefinedFooConfig extraConfig) {\n      super(\n          FooExtension.class.getName(),\n          Collections.emptyMap(),\n          new Action<ExtensionDefinedFooConfig>() {\n            @Override\n            public void execute(ExtensionDefinedFooConfig instance) {\n              instance.fooParam = extraConfig.fooParam;\n            }\n          });\n    }\n  }\n\n  private static class BarExtensionConfig extends BaseExtensionConfig<ExtensionDefinedBarConfig> {\n\n    private BarExtensionConfig() {\n      super(BarExtension.class.getName(), Collections.emptyMap(), null);\n    }\n\n    private BarExtensionConfig(ExtensionDefinedBarConfig extraConfig) {\n      super(\n          BarExtension.class.getName(),\n          Collections.emptyMap(),\n          new Action<ExtensionDefinedBarConfig>() {\n            @Override\n            public void execute(ExtensionDefinedBarConfig instance) {\n              instance.barParam = extraConfig.barParam;\n            }\n          });\n    }\n  }\n\n  // Not to be confused with Jib's plugin extension config. This class is for an extension-defined\n  // config specific to a third-party extension.\n  private static class ExtensionDefinedFooConfig {\n\n    private String fooParam;\n\n    private ExtensionDefinedFooConfig(String fooParam) {\n      this.fooParam = fooParam;\n    }\n  }\n\n  private static class ExtensionDefinedBarConfig {\n\n    private String barParam;\n\n    private ExtensionDefinedBarConfig(String barParam) {\n      this.barParam = barParam;\n    }\n  }\n\n  @Mock private TempDirectoryProvider mockTempDirectoryProvider;\n  @Mock private Logger mockLogger;\n  @Mock private ObjectFactory mockObjectFactory;\n\n  @Mock(answer = Answers.RETURNS_DEEP_STUBS)\n  private Project mockProject;\n\n  private List<JibGradlePluginExtension<?>> loadedExtensions = Collections.emptyList();\n  private final JibContainerBuilder containerBuilder = Jib.fromScratch();\n\n  private GradleProjectProperties gradleProjectProperties;\n\n  @Before\n  public void setUp() {\n    Mockito.when(mockLogger.isDebugEnabled()).thenReturn(true);\n    Mockito.when(mockLogger.isInfoEnabled()).thenReturn(true);\n    Mockito.when(mockLogger.isWarnEnabled()).thenReturn(true);\n    Mockito.when(mockLogger.isErrorEnabled()).thenReturn(true);\n\n    Mockito.when(mockProject.getGradle().getStartParameter().getConsoleOutput())\n        .thenReturn(ConsoleOutput.Plain);\n    Mockito.when(mockProject.getObjects()).thenReturn(mockObjectFactory);\n    Mockito.when(\n            mockObjectFactory.newInstance(\n                Mockito.eq(ExtensionDefinedFooConfig.class), Mockito.any()))\n        .thenReturn(new ExtensionDefinedFooConfig(\"uninitialized\"));\n    Mockito.when(\n            mockObjectFactory.newInstance(\n                Mockito.eq(ExtensionDefinedBarConfig.class), Mockito.any()))\n        .thenReturn(new ExtensionDefinedBarConfig(\"uninitialized\"));\n\n    gradleProjectProperties =\n        new GradleProjectProperties(\n            mockProject,\n            mockLogger,\n            mockTempDirectoryProvider,\n            () -> loadedExtensions,\n            JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);\n  }\n\n  @Test\n  public void testRunPluginExtensions_noExtensionsConfigured() throws JibPluginExtensionException {\n    FooExtension extension =\n        new FooExtension((buildPlan, properties, extraConfig, gradleData, logger) -> buildPlan);\n    loadedExtensions = Arrays.asList(extension);\n\n    JibContainerBuilder extendedBuilder =\n        gradleProjectProperties.runPluginExtensions(Collections.emptyList(), containerBuilder);\n    Assert.assertSame(extendedBuilder, containerBuilder);\n\n    gradleProjectProperties.waitForLoggingThread();\n    Mockito.verify(mockLogger).debug(\"No Jib plugin extensions configured to load\");\n  }\n\n  @Test\n  public void testRunPluginExtensions_configuredExtensionNotFound() {\n    try {\n      gradleProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(\n          \"extension configured but not discovered on Jib runtime classpath: com.google.cloud.\"\n              + \"tools.jib.gradle.GradleProjectPropertiesExtensionTest$FooExtension\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions() throws JibPluginExtensionException {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, gradleData, logger) -> {\n              logger.log(LogLevel.ERROR, \"awesome error from my extension\");\n              return buildPlan.toBuilder().setUser(\"user from extension\").build();\n            });\n    loadedExtensions = Arrays.asList(extension);\n\n    JibContainerBuilder extendedBuilder =\n        gradleProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"user from extension\", extendedBuilder.toContainerBuildPlan().getUser());\n\n    gradleProjectProperties.waitForLoggingThread();\n    Mockito.verify(mockLogger).error(\"awesome error from my extension\");\n    Mockito.verify(mockLogger)\n        .lifecycle(\n            Mockito.startsWith(\n                \"Running extension: com.google.cloud.tools.jib.gradle.GradleProjectProperties\"));\n  }\n\n  @Test\n  public void testRunPluginExtensions_exceptionFromExtension() {\n    FileNotFoundException fakeException = new FileNotFoundException();\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, gradleData, logger) -> {\n              throw new JibPluginExtensionException(\n                  JibGradlePluginExtension.class, \"exception from extension\", fakeException);\n            });\n    loadedExtensions = Arrays.asList(extension);\n\n    try {\n      gradleProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(\"exception from extension\", ex.getMessage());\n      Assert.assertSame(fakeException, ex.getCause());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_invalidBaseImageFromExtension() {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, gradleData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\" in*val+id\").build());\n    loadedExtensions = Arrays.asList(extension);\n\n    try {\n      gradleProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(\"invalid base image reference:  in*val+id\", ex.getMessage());\n      MatcherAssert.assertThat(\n          ex.getCause(), CoreMatchers.instanceOf(InvalidImageReferenceException.class));\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_extensionOrder() throws JibPluginExtensionException {\n    FooExtension fooExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, gradleData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\"foo\").build());\n    BarExtension barExtension =\n        new BarExtension(\n            (buildPlan, properties, extraConfig, gradleData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\"bar\").build());\n    loadedExtensions = Arrays.asList(fooExtension, barExtension);\n\n    JibContainerBuilder extendedBuilder1 =\n        gradleProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig(), new BarExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"bar\", extendedBuilder1.toContainerBuildPlan().getBaseImage());\n\n    JibContainerBuilder extendedBuilder2 =\n        gradleProjectProperties.runPluginExtensions(\n            Arrays.asList(new BarExtensionConfig(), new FooExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"foo\", extendedBuilder2.toContainerBuildPlan().getBaseImage());\n  }\n\n  @Test\n  public void testRunPluginExtensions_customProperties() throws JibPluginExtensionException {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, gradleData, logger) ->\n                buildPlan.toBuilder().setUser(properties.get(\"user\")).build());\n    loadedExtensions = Arrays.asList(extension);\n\n    JibContainerBuilder extendedBuilder =\n        gradleProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig(ImmutableMap.of(\"user\", \"65432\"))),\n            containerBuilder);\n    Assert.assertEquals(\"65432\", extendedBuilder.toContainerBuildPlan().getUser());\n  }\n\n  @Test\n  public void testRunPluginExtensions_extensionDefinedConfigurations_emptyConfig()\n      throws JibPluginExtensionException {\n    FooExtension fooExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(Optional.empty(), extraConfig);\n              return buildPlan;\n            });\n    BarExtension barExtension =\n        new BarExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(Optional.empty(), extraConfig);\n              return buildPlan;\n            });\n    loadedExtensions = Arrays.asList(fooExtension, barExtension);\n\n    gradleProjectProperties.runPluginExtensions(\n        Arrays.asList(new FooExtensionConfig(), new BarExtensionConfig()), containerBuilder);\n  }\n\n  @Test\n  public void testRunPluginExtensions_extensionDefinedConfigurations()\n      throws JibPluginExtensionException {\n    FooExtension fooExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(\"fooParamValue\", extraConfig.get().fooParam);\n              return buildPlan;\n            });\n    BarExtension barExtension =\n        new BarExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(\"barParamValue\", extraConfig.get().barParam);\n              return buildPlan;\n            });\n    loadedExtensions = Arrays.asList(fooExtension, barExtension);\n\n    gradleProjectProperties.runPluginExtensions(\n        Arrays.asList(\n            new FooExtensionConfig(new ExtensionDefinedFooConfig(\"fooParamValue\")),\n            new BarExtensionConfig(new ExtensionDefinedBarConfig(\"barParamValue\"))),\n        containerBuilder);\n  }\n\n  @Test\n  public void testRunPluginExtensions_ignoreUnexpectedExtraConfig()\n      throws JibPluginExtensionException {\n    BaseExtension<Void> extension =\n        new BaseExtension<>(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> buildPlan, null);\n    loadedExtensions = Arrays.asList(extension);\n\n    ExtensionConfiguration extensionConfig =\n        new BaseExtensionConfig<>(\n            BaseExtension.class.getName(), Collections.emptyMap(), (ignored) -> {});\n    try {\n      gradleProjectProperties.runPluginExtensions(Arrays.asList(extensionConfig), containerBuilder);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\n          \"extension BaseExtension does not expect extension-specific configuration; remove the \"\n              + \"inapplicable 'pluginExtension.configuration' from Gradle build script\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_runtimeExceptionFromExtension() {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              throw new IndexOutOfBoundsException(\"buggy extension\");\n            });\n    loadedExtensions = Arrays.asList(extension);\n\n    try {\n      gradleProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(FooExtension.class, ex.getExtensionClass());\n      Assert.assertEquals(\"extension crashed: buggy extension\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.gradle.extension.JibGradlePluginExtension;\nimport com.google.cloud.tools.jib.plugins.common.ContainerizingMode;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport com.google.common.truth.Correspondence;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.StringJoiner;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\nimport org.gradle.api.JavaVersion;\nimport org.gradle.api.Project;\nimport org.gradle.api.artifacts.dsl.DependencyHandler;\nimport org.gradle.api.file.FileCollection;\nimport org.gradle.api.java.archives.internal.DefaultManifest;\nimport org.gradle.api.logging.Logger;\nimport org.gradle.api.plugins.JavaPlugin;\nimport org.gradle.api.plugins.JavaPluginExtension;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.tasks.bundling.War;\nimport org.gradle.jvm.tasks.Jar;\nimport org.gradle.testfixtures.ProjectBuilder;\nimport org.hamcrest.CoreMatchers;\nimport org.junit.Assume;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Test for {@link GradleProjectProperties}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class GradleProjectPropertiesTest {\n\n  private static final Correspondence<FileEntry, Path> SOURCE_FILE_OF =\n      Correspondence.transforming(FileEntry::getSourceFile, \"has sourceFile of\");\n  private static final Correspondence<FileEntry, String> EXTRACTION_PATH_OF =\n      Correspondence.transforming(\n          entry -> entry.getExtractionPath().toString(), \"has extractionPath of\");\n  private static final Correspondence<File, Path> FILE_PATH_OF =\n      Correspondence.transforming(File::toPath, \"has Path of\");\n\n  private static final Instant EPOCH_PLUS_32 = Instant.ofEpochSecond(32);\n\n  /** Helper for reading back layers in a {@link BuildContext}. */\n  private static class ContainerBuilderLayers {\n\n    private final FileEntriesLayer resourcesLayer;\n    private final FileEntriesLayer classesLayer;\n    private final FileEntriesLayer dependenciesLayer;\n    private final FileEntriesLayer snapshotsLayer;\n\n    private ContainerBuilderLayers(BuildContext buildContext) {\n      resourcesLayer = getLayerByName(buildContext, LayerType.RESOURCES.getName());\n      classesLayer = getLayerByName(buildContext, LayerType.CLASSES.getName());\n      dependenciesLayer = getLayerByName(buildContext, LayerType.DEPENDENCIES.getName());\n      snapshotsLayer = getLayerByName(buildContext, LayerType.SNAPSHOT_DEPENDENCIES.getName());\n    }\n\n    private static FileEntriesLayer getLayerByName(BuildContext buildContext, String name) {\n      List<FileEntriesLayer> layers = buildContext.getLayerConfigurations();\n      return layers.stream().filter(layer -> layer.getName().equals(name)).findFirst().get();\n    }\n  }\n\n  private static Path getResource(String path) throws URISyntaxException {\n    return Paths.get(Resources.getResource(path).toURI());\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock private TempDirectoryProvider mockTempDirectoryProvider;\n  @Mock private Supplier<List<JibGradlePluginExtension<?>>> mockExtensionLoader;\n  @Mock private Logger mockLogger;\n\n  private GradleProjectProperties gradleProjectProperties;\n  private Project project;\n\n  @Before\n  public void setUp() throws URISyntaxException, IOException {\n    when(mockLogger.isDebugEnabled()).thenReturn(true);\n    when(mockLogger.isInfoEnabled()).thenReturn(true);\n    when(mockLogger.isWarnEnabled()).thenReturn(true);\n    when(mockLogger.isErrorEnabled()).thenReturn(true);\n\n    Path projectDir = getResource(\"gradle/application\");\n    project =\n        ProjectBuilder.builder()\n            .withName(\"my-app\")\n            .withProjectDir(projectDir.toFile())\n            .withGradleUserHomeDir(temporaryFolder.newFolder())\n            .build();\n    project.getPlugins().apply(\"java\");\n\n    DependencyHandler dependencies = project.getDependencies();\n    dependencies.add(\n        \"implementation\",\n        project.files(\n            \"dependencies/library.jarC.jar\",\n            \"dependencies/libraryB.jar\",\n            \"dependencies/libraryA.jar\",\n            \"dependencies/dependency-1.0.0.jar\",\n            \"dependencies/more/dependency-1.0.0.jar\",\n            \"dependencies/another/one/dependency-1.0.0.jar\",\n            \"dependencies/dependencyX-1.0.0-SNAPSHOT.jar\"));\n\n    // We can't commit an empty directory in Git, so create (if not exist).\n    Path emptyDirectory = getResource(\"gradle/webapp\").resolve(\"WEB-INF/classes/empty_dir\");\n    Files.createDirectories(emptyDirectory);\n\n    gradleProjectProperties =\n        new GradleProjectProperties(\n            project,\n            mockLogger,\n            mockTempDirectoryProvider,\n            mockExtensionLoader,\n            JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);\n  }\n\n  @Test\n  public void testGetMainClassFromJar_success() {\n    Jar jar = project.getTasks().withType(Jar.class).getByName(\"jar\");\n    jar.setManifest(\n        new DefaultManifest(null).attributes(ImmutableMap.of(\"Main-Class\", \"some.main.class\")));\n\n    assertThat(gradleProjectProperties.getMainClassFromJarPlugin()).isEqualTo(\"some.main.class\");\n  }\n\n  @Test\n  public void testGetMainClassFromJar_missing() {\n    assertThat(gradleProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testGetMainClassFromJarAsProperty_success() {\n    Property<String> mainClass =\n        project.getObjects().property(String.class).value(\"some.main.class\");\n\n    Jar jar = project.getTasks().withType(Jar.class).getByName(\"jar\");\n    jar.setManifest(new DefaultManifest(null).attributes(ImmutableMap.of(\"Main-Class\", mainClass)));\n\n    assertThat(gradleProjectProperties.getMainClassFromJarPlugin()).isEqualTo(\"some.main.class\");\n  }\n\n  @Test\n  public void testGetMainClassFromJarAsPropertyWithValueNull_missing() {\n    Property<String> mainClass = project.getObjects().property(String.class).value((String) null);\n\n    Jar jar = project.getTasks().withType(Jar.class).getByName(\"jar\");\n    jar.setManifest(new DefaultManifest(null).attributes(ImmutableMap.of(\"Main-Class\", mainClass)));\n\n    assertThat(gradleProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testIsWarProject() {\n    project.getPlugins().apply(\"war\");\n    assertThat(gradleProjectProperties.isWarProject()).isTrue();\n  }\n\n  @Test\n  public void testGetInputFiles() throws URISyntaxException {\n    Path applicationDirectory = getResource(\"gradle/application\");\n\n    List<Path> extraDirectories = Arrays.asList(applicationDirectory.resolve(\"extra-directory\"));\n    FileCollection fileCollection =\n        GradleProjectProperties.getInputFiles(\n            project, extraDirectories, JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);\n\n    assertThat(fileCollection)\n        .comparingElementsUsing(FILE_PATH_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"build/classes/java/main\"),\n            applicationDirectory.resolve(\"build/resources/main\"),\n            applicationDirectory.resolve(\"dependencies/dependencyX-1.0.0-SNAPSHOT.jar\"),\n            applicationDirectory.resolve(\"dependencies/dependency-1.0.0.jar\"),\n            applicationDirectory.resolve(\"dependencies/more/dependency-1.0.0.jar\"),\n            applicationDirectory.resolve(\"dependencies/another/one/dependency-1.0.0.jar\"),\n            applicationDirectory.resolve(\"dependencies/libraryA.jar\"),\n            applicationDirectory.resolve(\"dependencies/libraryB.jar\"),\n            applicationDirectory.resolve(\"dependencies/library.jarC.jar\"),\n            applicationDirectory.resolve(\"extra-directory\"));\n  }\n\n  @Test\n  public void testConvertPermissionsMap() {\n    Map<String, String> map = ImmutableMap.of(\"/test/folder/file1\", \"123\", \"/test/file2\", \"456\");\n    assertThat(TaskCommon.convertPermissionsMap(map))\n        .containsExactly(\n            \"/test/folder/file1\",\n            FilePermissions.fromOctalString(\"123\"),\n            \"/test/file2\",\n            FilePermissions.fromOctalString(\"456\"))\n        .inOrder();\n\n    Exception exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> TaskCommon.convertPermissionsMap(ImmutableMap.of(\"path\", \"invalid permission\")));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"octalPermissions must be a 3-digit octal number (000-777)\");\n  }\n\n  @Test\n  public void testGetMajorJavaVersion() {\n    JavaPluginExtension extension = project.getExtensions().findByType(JavaPluginExtension.class);\n\n    extension.setTargetCompatibility(JavaVersion.VERSION_1_3);\n    assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(3);\n\n    extension.setTargetCompatibility(JavaVersion.VERSION_11);\n    assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(11);\n\n    extension.setTargetCompatibility(JavaVersion.VERSION_1_9);\n    assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(9);\n  }\n\n  @Test\n  public void testGetMajorJavaVersion_jvm8() {\n    Assume.assumeThat(JavaVersion.current(), CoreMatchers.is(JavaVersion.VERSION_1_8));\n\n    assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(8);\n  }\n\n  @Test\n  public void testGetMajorJavaVersion_jvm11() {\n    Assume.assumeThat(JavaVersion.current(), CoreMatchers.is(JavaVersion.VERSION_11));\n\n    assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(11);\n  }\n\n  @Test\n  public void testCreateContainerBuilder_correctSourceFiles()\n      throws URISyntaxException, InvalidImageReferenceException, CacheDirectoryCreationException {\n    ContainerBuilderLayers layers = new ContainerBuilderLayers(setupBuildContext());\n\n    Path applicationDirectory = getResource(\"gradle/application\");\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"dependencies/dependencyX-1.0.0-SNAPSHOT.jar\"));\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"dependencies/dependency-1.0.0.jar\"),\n            applicationDirectory.resolve(\"dependencies/more/dependency-1.0.0.jar\"),\n            applicationDirectory.resolve(\"dependencies/another/one/dependency-1.0.0.jar\"),\n            applicationDirectory.resolve(\"dependencies/libraryA.jar\"),\n            applicationDirectory.resolve(\"dependencies/libraryB.jar\"),\n            applicationDirectory.resolve(\"dependencies/library.jarC.jar\"));\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"build/resources/main/resourceA\"),\n            applicationDirectory.resolve(\"build/resources/main/resourceB\"),\n            applicationDirectory.resolve(\"build/resources/main/world\"));\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"build/classes/java/main/HelloWorld.class\"),\n            applicationDirectory.resolve(\"build/classes/java/main/some.class\"));\n\n    List<FileEntry> allFileEntries = new ArrayList<>();\n    allFileEntries.addAll(layers.snapshotsLayer.getEntries());\n    allFileEntries.addAll(layers.dependenciesLayer.getEntries());\n    allFileEntries.addAll(layers.classesLayer.getEntries());\n    allFileEntries.addAll(layers.resourcesLayer.getEntries());\n    Set<Instant> modificationTimes =\n        allFileEntries.stream().map(FileEntry::getModificationTime).collect(Collectors.toSet());\n    assertThat(modificationTimes).containsExactly(EPOCH_PLUS_32);\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noClassesFiles()\n      throws InvalidImageReferenceException, IOException {\n    Project project =\n        ProjectBuilder.builder()\n            .withProjectDir(temporaryFolder.newFolder())\n            .withGradleUserHomeDir(temporaryFolder.newFolder())\n            .build();\n    project.getPlugins().apply(\"java\");\n\n    gradleProjectProperties =\n        new GradleProjectProperties(\n            project,\n            mockLogger,\n            mockTempDirectoryProvider,\n            mockExtensionLoader,\n            JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);\n\n    gradleProjectProperties.createJibContainerBuilder(\n        JavaContainerBuilder.from(RegistryImage.named(\"base\")), ContainerizingMode.EXPLODED);\n    gradleProjectProperties.waitForLoggingThread();\n    verify(mockLogger).warn(\"No classes files were found - did you compile your project?\");\n  }\n\n  @Test\n  public void testCreateContainerBuilder_correctExtractionPaths()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException {\n    ContainerBuilderLayers layers = new ContainerBuilderLayers(setupBuildContext());\n\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/libs/dependency-1.0.0-770.jar\",\n            \"/my/app/libs/dependency-1.0.0-200.jar\",\n            \"/my/app/libs/dependency-1.0.0-480.jar\",\n            \"/my/app/libs/libraryA.jar\",\n            \"/my/app/libs/libraryB.jar\",\n            \"/my/app/libs/library.jarC.jar\");\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/libs/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/resources/resourceA\",\n            \"/my/app/resources/resourceB\",\n            \"/my/app/resources/world\");\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/classes/HelloWorld.class\", \"/my/app/classes/some.class\");\n  }\n\n  @Test\n  public void testCreateContainerBuilder_war()\n      throws URISyntaxException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    Path unzipTarget = setUpWarProject(getResource(\"gradle/webapp\"));\n\n    ContainerBuilderLayers layers = new ContainerBuilderLayers(setupBuildContext());\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(unzipTarget.resolve(\"WEB-INF/lib/dependency-1.0.0.jar\"));\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(unzipTarget.resolve(\"WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\"));\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            unzipTarget.resolve(\"META-INF\"),\n            unzipTarget.resolve(\"META-INF/context.xml\"),\n            unzipTarget.resolve(\"Test.jsp\"),\n            unzipTarget.resolve(\"WEB-INF\"),\n            unzipTarget.resolve(\"WEB-INF/classes\"),\n            unzipTarget.resolve(\"WEB-INF/classes/empty_dir\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package/test.properties\"),\n            unzipTarget.resolve(\"WEB-INF/lib\"),\n            unzipTarget.resolve(\"WEB-INF/web.xml\"));\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            unzipTarget.resolve(\"WEB-INF/classes/HelloWorld.class\"),\n            unzipTarget.resolve(\"WEB-INF/classes/empty_dir\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package/Other.class\"));\n\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependency-1.0.0.jar\");\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/META-INF\",\n            \"/my/app/META-INF/context.xml\",\n            \"/my/app/Test.jsp\",\n            \"/my/app/WEB-INF\",\n            \"/my/app/WEB-INF/classes\",\n            \"/my/app/WEB-INF/classes/empty_dir\",\n            \"/my/app/WEB-INF/classes/package\",\n            \"/my/app/WEB-INF/classes/package/test.properties\",\n            \"/my/app/WEB-INF/lib\",\n            \"/my/app/WEB-INF/web.xml\");\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/WEB-INF/classes/HelloWorld.class\",\n            \"/my/app/WEB-INF/classes/empty_dir\",\n            \"/my/app/WEB-INF/classes/package\",\n            \"/my/app/WEB-INF/classes/package/Other.class\");\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noErrorIfWebInfClassesDoesNotExist()\n      throws IOException, InvalidImageReferenceException {\n    temporaryFolder.newFolder(\"WEB-INF\", \"lib\");\n    setUpWarProject(temporaryFolder.getRoot().toPath());\n\n    assertThat(\n            gradleProjectProperties.createJibContainerBuilder(\n                JavaContainerBuilder.from(\"ignored\"), ContainerizingMode.EXPLODED))\n        .isNotNull();\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noErrorIfWebInfLibDoesNotExist()\n      throws IOException, InvalidImageReferenceException {\n    temporaryFolder.newFolder(\"WEB-INF\", \"classes\");\n    setUpWarProject(temporaryFolder.getRoot().toPath());\n\n    assertThat(\n            gradleProjectProperties.createJibContainerBuilder(\n                JavaContainerBuilder.from(\"ignored\"), ContainerizingMode.EXPLODED))\n        .isNotNull();\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noErrorIfWebInfDoesNotExist()\n      throws IOException, InvalidImageReferenceException {\n    setUpWarProject(temporaryFolder.getRoot().toPath());\n\n    assertThat(\n            gradleProjectProperties.createJibContainerBuilder(\n                JavaContainerBuilder.from(\"ignored\"), ContainerizingMode.EXPLODED))\n        .isNotNull();\n  }\n\n  @Test\n  public void testGetWarFilePath() throws IOException {\n    Path outputDir = temporaryFolder.newFolder(\"output\").toPath();\n\n    project.getPlugins().apply(\"war\");\n    War war = project.getTasks().withType(War.class).getByName(\"war\");\n    war.getDestinationDirectory().set(outputDir.toFile());\n\n    assertThat(gradleProjectProperties.getWarFilePath())\n        .isEqualTo(outputDir.resolve(\"my-app.war\").toString());\n  }\n\n  @Test\n  public void testGetWarFilePath_bootWar() throws IOException {\n    Path outputDir = temporaryFolder.newFolder(\"output\").toPath();\n\n    project.getPlugins().apply(\"war\");\n    project.getPlugins().apply(\"org.springframework.boot\");\n    War bootWar = project.getTasks().withType(War.class).getByName(\"bootWar\");\n    bootWar.getDestinationDirectory().set(outputDir.toFile());\n\n    assertThat(gradleProjectProperties.getWarFilePath())\n        .isEqualTo(outputDir.resolve(\"my-app.war\").toString());\n  }\n\n  @Test\n  public void testGetWarFilePath_bootWarDisabled() throws IOException {\n    Path outputDir = temporaryFolder.newFolder(\"output\").toPath();\n\n    project.getPlugins().apply(\"war\");\n    War war = project.getTasks().withType(War.class).getByName(\"war\");\n    war.getDestinationDirectory().set(outputDir.toFile());\n\n    project.getPlugins().apply(\"org.springframework.boot\");\n    project.getTasks().getByName(\"bootWar\").setEnabled(false);\n\n    assertThat(gradleProjectProperties.getWarFilePath())\n        .isEqualTo(outputDir.resolve(\"my-app.war\").toString());\n  }\n\n  @Test\n  public void testGetDependencies() throws URISyntaxException {\n    assertThat(gradleProjectProperties.getDependencies())\n        .containsExactly(\n            getResource(\"gradle/application/dependencies/library.jarC.jar\"),\n            getResource(\"gradle/application/dependencies/libraryB.jar\"),\n            getResource(\"gradle/application/dependencies/libraryA.jar\"),\n            getResource(\"gradle/application/dependencies/dependency-1.0.0.jar\"),\n            getResource(\"gradle/application/dependencies/more/dependency-1.0.0.jar\"),\n            getResource(\"gradle/application/dependencies/another/one/dependency-1.0.0.jar\"),\n            getResource(\"gradle/application/dependencies/dependencyX-1.0.0-SNAPSHOT.jar\"))\n        .inOrder();\n  }\n\n  private BuildContext setupBuildContext()\n      throws InvalidImageReferenceException, CacheDirectoryCreationException {\n    JavaContainerBuilder javaContainerBuilder =\n        JavaContainerBuilder.from(RegistryImage.named(\"base\"))\n            .setAppRoot(AbsoluteUnixPath.get(\"/my/app\"))\n            .setModificationTimeProvider((ignored1, ignored2) -> EPOCH_PLUS_32);\n    JibContainerBuilder jibContainerBuilder =\n        gradleProjectProperties.createJibContainerBuilder(\n            javaContainerBuilder, ContainerizingMode.EXPLODED);\n    return JibContainerBuilderTestHelper.toBuildContext(\n        jibContainerBuilder, Containerizer.to(RegistryImage.named(\"to\")));\n  }\n\n  private Path setUpWarProject(Path webAppDirectory) throws IOException {\n    File warOutputDir = temporaryFolder.newFolder(\"output\");\n    zipUpDirectory(webAppDirectory, warOutputDir.toPath().resolve(\"my-app.war\"));\n\n    project.getPlugins().apply(\"war\");\n    War war = project.getTasks().withType(War.class).getByName(\"war\");\n    war.getDestinationDirectory().set(warOutputDir);\n\n    // Make \"GradleProjectProperties\" use this folder to explode the WAR into.\n    Path unzipTarget = temporaryFolder.newFolder(\"exploded\").toPath();\n    when(mockTempDirectoryProvider.newDirectory()).thenReturn(unzipTarget);\n    return unzipTarget;\n  }\n\n  private static Path zipUpDirectory(Path sourceRoot, Path targetZip) throws IOException {\n    try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(targetZip))) {\n      for (Path source : new DirectoryWalker(sourceRoot).filterRoot().walk()) {\n\n        StringJoiner pathJoiner = new StringJoiner(\"/\", \"\", \"\");\n        sourceRoot.relativize(source).forEach(element -> pathJoiner.add(element.toString()));\n        String zipEntryPath =\n            Files.isDirectory(source) ? pathJoiner.toString() + '/' : pathJoiner.toString();\n\n        ZipEntry entry = new ZipEntry(zipEntryPath);\n        zipOut.putNextEntry(entry);\n        if (!Files.isDirectory(source)) {\n          try (InputStream in = Files.newInputStream(source)) {\n            ByteStreams.copy(in, zipOut);\n          }\n        }\n        zipOut.closeEntry();\n      }\n    }\n    return targetZip;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Sets;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Optional;\nimport org.gradle.api.provider.MapProperty;\nimport org.gradle.api.provider.Property;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Test for {@link GradleRawConfiguration}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class GradleRawConfigurationTest {\n\n  @Mock private MapProperty<String, String> labels;\n\n  @Test\n  public void testGetters() {\n    JibExtension jibExtension = Mockito.mock(JibExtension.class);\n\n    AuthParameters authParameters = Mockito.mock(AuthParameters.class);\n    BaseImageParameters baseImageParameters = Mockito.mock(BaseImageParameters.class);\n    TargetImageParameters targetImageParameters = Mockito.mock(TargetImageParameters.class);\n    ContainerParameters containerParameters = Mockito.mock(ContainerParameters.class);\n    DockerClientParameters dockerClientParameters = Mockito.mock(DockerClientParameters.class);\n    OutputPathsParameters outputPathsParameters = Mockito.mock(OutputPathsParameters.class);\n    CredHelperParameters fromCredHelperParameters = Mockito.mock(CredHelperParameters.class);\n    CredHelperParameters toCredHelperParameters = Mockito.mock(CredHelperParameters.class);\n    Property<String> filesModificationTime = Mockito.mock(Property.class);\n    Property<String> creationTime = Mockito.mock(Property.class);\n\n    Mockito.when(authParameters.getUsername()).thenReturn(\"user\");\n    Mockito.when(authParameters.getPassword()).thenReturn(\"password\");\n    Mockito.when(authParameters.getAuthDescriptor()).thenReturn(\"from.auth\");\n    Mockito.when(authParameters.getUsernameDescriptor()).thenReturn(\"from.auth.username\");\n    Mockito.when(authParameters.getPasswordDescriptor()).thenReturn(\"from.auth.password\");\n\n    Mockito.when(jibExtension.getFrom()).thenReturn(baseImageParameters);\n    Mockito.when(jibExtension.getTo()).thenReturn(targetImageParameters);\n    Mockito.when(jibExtension.getContainer()).thenReturn(containerParameters);\n    Mockito.when(jibExtension.getDockerClient()).thenReturn(dockerClientParameters);\n    Mockito.when(jibExtension.getOutputPaths()).thenReturn(outputPathsParameters);\n    Mockito.when(jibExtension.getAllowInsecureRegistries()).thenReturn(true);\n\n    Mockito.when(fromCredHelperParameters.getHelperName()).thenReturn(Optional.of(\"gcr\"));\n    Mockito.when(fromCredHelperParameters.getEnvironment())\n        .thenReturn(Collections.singletonMap(\"ENV_VARIABLE\", \"Value1\"));\n    Mockito.when(baseImageParameters.getCredHelper()).thenReturn(fromCredHelperParameters);\n    Mockito.when(baseImageParameters.getImage()).thenReturn(\"openjdk:15\");\n    Mockito.when(baseImageParameters.getAuth()).thenReturn(authParameters);\n\n    Mockito.when(targetImageParameters.getTags())\n        .thenReturn(new HashSet<>(Arrays.asList(\"additional\", \"tags\")));\n    Mockito.when(toCredHelperParameters.getHelperName()).thenReturn(Optional.of(\"ecr-login\"));\n    Mockito.when(toCredHelperParameters.getEnvironment())\n        .thenReturn(Collections.singletonMap(\"ENV_VARIABLE\", \"Value2\"));\n    Mockito.when(targetImageParameters.getCredHelper()).thenReturn(toCredHelperParameters);\n\n    Mockito.when(containerParameters.getAppRoot()).thenReturn(\"/app/root\");\n    Mockito.when(containerParameters.getArgs()).thenReturn(Arrays.asList(\"--log\", \"info\"));\n    Mockito.when(containerParameters.getEntrypoint()).thenReturn(Arrays.asList(\"java\", \"Main\"));\n    Mockito.when(containerParameters.getEnvironment())\n        .thenReturn(new HashMap<>(ImmutableMap.of(\"currency\", \"dollar\")));\n    Mockito.when(containerParameters.getJvmFlags()).thenReturn(Arrays.asList(\"-cp\", \".\"));\n    Mockito.when(labels.get()).thenReturn(Collections.singletonMap(\"unit\", \"cm\"));\n    Mockito.when(containerParameters.getLabels()).thenReturn(labels);\n    Mockito.when(containerParameters.getMainClass()).thenReturn(\"com.example.Main\");\n    Mockito.when(containerParameters.getPorts()).thenReturn(Arrays.asList(\"80/tcp\", \"0\"));\n    Mockito.when(containerParameters.getUser()).thenReturn(\"admin:wheel\");\n    Mockito.when(containerParameters.getFilesModificationTime()).thenReturn(filesModificationTime);\n    Mockito.when(filesModificationTime.get()).thenReturn(\"2011-12-03T22:42:05Z\");\n    Mockito.when(containerParameters.getCreationTime()).thenReturn(creationTime);\n    Mockito.when(creationTime.get()).thenReturn(\"2011-12-03T11:42:05Z\");\n\n    Mockito.when(dockerClientParameters.getExecutablePath()).thenReturn(Paths.get(\"test\"));\n    Mockito.when(dockerClientParameters.getEnvironment())\n        .thenReturn(new HashMap<>(ImmutableMap.of(\"docker\", \"client\")));\n\n    Mockito.when(outputPathsParameters.getDigestPath()).thenReturn(Paths.get(\"digest/path\"));\n    Mockito.when(outputPathsParameters.getImageIdPath()).thenReturn(Paths.get(\"id/path\"));\n    Mockito.when(outputPathsParameters.getImageJsonPath()).thenReturn(Paths.get(\"json/path\"));\n    Mockito.when(outputPathsParameters.getTarPath()).thenReturn(Paths.get(\"tar/path\"));\n\n    GradleRawConfiguration rawConfiguration = new GradleRawConfiguration(jibExtension);\n\n    AuthProperty fromAuth = rawConfiguration.getFromAuth();\n    Assert.assertEquals(\"user\", fromAuth.getUsername());\n    Assert.assertEquals(\"password\", fromAuth.getPassword());\n    Assert.assertEquals(\"from.auth\", fromAuth.getAuthDescriptor());\n    Assert.assertEquals(\"from.auth.username\", fromAuth.getUsernameDescriptor());\n    Assert.assertEquals(\"from.auth.password\", fromAuth.getPasswordDescriptor());\n\n    Assert.assertTrue(rawConfiguration.getAllowInsecureRegistries());\n    Assert.assertEquals(\"/app/root\", rawConfiguration.getAppRoot());\n    Assert.assertEquals(Arrays.asList(\"java\", \"Main\"), rawConfiguration.getEntrypoint().get());\n    Assert.assertEquals(\n        new HashMap<>(ImmutableMap.of(\"currency\", \"dollar\")), rawConfiguration.getEnvironment());\n    Assert.assertEquals(\"gcr\", rawConfiguration.getFromCredHelper().getHelperName().get());\n    Assert.assertEquals(\n        Collections.singletonMap(\"ENV_VARIABLE\", \"Value1\"),\n        rawConfiguration.getFromCredHelper().getEnvironment());\n    Assert.assertEquals(\"openjdk:15\", rawConfiguration.getFromImage().get());\n    Assert.assertEquals(Arrays.asList(\"-cp\", \".\"), rawConfiguration.getJvmFlags());\n    Assert.assertEquals(new HashMap<>(ImmutableMap.of(\"unit\", \"cm\")), rawConfiguration.getLabels());\n    Assert.assertEquals(\"com.example.Main\", rawConfiguration.getMainClass().get());\n    Assert.assertEquals(Arrays.asList(\"80/tcp\", \"0\"), rawConfiguration.getPorts());\n    Assert.assertEquals(\n        Arrays.asList(\"--log\", \"info\"), rawConfiguration.getProgramArguments().get());\n    Assert.assertEquals(\n        new HashSet<>(Arrays.asList(\"additional\", \"tags\")),\n        Sets.newHashSet(rawConfiguration.getToTags()));\n    Assert.assertEquals(\"ecr-login\", rawConfiguration.getToCredHelper().getHelperName().get());\n    Assert.assertEquals(\n        Collections.singletonMap(\"ENV_VARIABLE\", \"Value2\"),\n        rawConfiguration.getToCredHelper().getEnvironment());\n    Assert.assertEquals(\"admin:wheel\", rawConfiguration.getUser().get());\n    Assert.assertEquals(\"2011-12-03T22:42:05Z\", rawConfiguration.getFilesModificationTime());\n    Assert.assertEquals(\"2011-12-03T11:42:05Z\", rawConfiguration.getCreationTime());\n    Assert.assertEquals(Paths.get(\"test\"), rawConfiguration.getDockerExecutable().get());\n    Assert.assertEquals(\n        new HashMap<>(ImmutableMap.of(\"docker\", \"client\")),\n        rawConfiguration.getDockerEnvironment());\n    Assert.assertEquals(Paths.get(\"digest/path\"), rawConfiguration.getDigestOutputPath());\n    Assert.assertEquals(Paths.get(\"id/path\"), rawConfiguration.getImageIdOutputPath());\n    Assert.assertEquals(Paths.get(\"json/path\"), rawConfiguration.getImageJsonOutputPath());\n    Assert.assertEquals(Paths.get(\"tar/path\"), rawConfiguration.getTarOutputPath());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.Assert.assertTrue;\n\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Properties;\nimport org.gradle.api.Project;\nimport org.gradle.api.provider.Property;\nimport org.gradle.api.provider.Provider;\nimport org.gradle.api.provider.ProviderFactory;\nimport org.gradle.testfixtures.ProjectBuilder;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\n\n/** Tests for {@link JibExtension}. */\npublic class JibExtensionTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  private JibExtension testJibExtension;\n  private Project fakeProject;\n\n  @Before\n  public void setUp() {\n    fakeProject = ProjectBuilder.builder().build();\n    testJibExtension =\n        fakeProject\n            .getExtensions()\n            .create(JibPlugin.JIB_EXTENSION_NAME, JibExtension.class, fakeProject);\n  }\n\n  @Test\n  public void testFrom() {\n    assertThat(testJibExtension.getFrom().getImage()).isNull();\n    assertThat(testJibExtension.getFrom().getCredHelper().getHelper()).isNull();\n\n    List<PlatformParameters> defaultPlatforms = testJibExtension.getFrom().getPlatforms().get();\n    assertThat(defaultPlatforms).hasSize(1);\n    assertThat(defaultPlatforms.get(0).getArchitecture()).isEqualTo(\"amd64\");\n    assertThat(defaultPlatforms.get(0).getOs()).isEqualTo(\"linux\");\n\n    testJibExtension.from(\n        from -> {\n          from.setImage(\"some image\");\n          from.setCredHelper(\"some cred helper\");\n          from.auth(auth -> auth.setUsername(\"some username\"));\n          from.auth(auth -> auth.setPassword(\"some password\"));\n          from.platforms(\n              platformSpec ->\n                  platformSpec.platform(\n                      platform -> {\n                        platform.setArchitecture(\"arm\");\n                        platform.setOs(\"windows\");\n                      }));\n        });\n    assertThat(testJibExtension.getFrom().getImage()).isEqualTo(\"some image\");\n    assertThat(testJibExtension.getFrom().getCredHelper().getHelper())\n        .isEqualTo(\"some cred helper\");\n    assertThat(testJibExtension.getFrom().getAuth().getUsername()).isEqualTo(\"some username\");\n    assertThat(testJibExtension.getFrom().getAuth().getPassword()).isEqualTo(\"some password\");\n\n    List<PlatformParameters> platforms = testJibExtension.getFrom().getPlatforms().get();\n    assertThat(platforms).hasSize(1);\n    assertThat(platforms.get(0).getArchitecture()).isEqualTo(\"arm\");\n    assertThat(platforms.get(0).getOs()).isEqualTo(\"windows\");\n  }\n\n  @Test\n  public void testFromCredHelperClosure() {\n    assertThat(testJibExtension.getFrom().getImage()).isNull();\n    assertThat(testJibExtension.getFrom().getCredHelper().getHelper()).isNull();\n\n    testJibExtension.from(\n        from -> {\n          from.setImage(\"some image\");\n          from.credHelper(\n              credHelper -> {\n                credHelper.setHelper(\"some cred helper\");\n                credHelper.setEnvironment(Collections.singletonMap(\"ENV_VARIABLE\", \"Value\"));\n              });\n        });\n    assertThat(testJibExtension.getFrom().getCredHelper().getHelper())\n        .isEqualTo(\"some cred helper\");\n    assertThat(testJibExtension.getFrom().getCredHelper().getEnvironment())\n        .isEqualTo(Collections.singletonMap(\"ENV_VARIABLE\", \"Value\"));\n  }\n\n  @Test\n  public void testTo() {\n    assertThat(testJibExtension.getTo().getImage()).isNull();\n    assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isNull();\n\n    testJibExtension.to(\n        to -> {\n          to.setImage(\"some image\");\n          to.setCredHelper(\"some cred helper\");\n          to.auth(auth -> auth.setUsername(\"some username\"));\n          to.auth(auth -> auth.setPassword(\"some password\"));\n        });\n    assertThat(testJibExtension.getTo().getImage()).isEqualTo(\"some image\");\n    assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isEqualTo(\"some cred helper\");\n    assertThat(testJibExtension.getTo().getAuth().getUsername()).isEqualTo(\"some username\");\n    assertThat(testJibExtension.getTo().getAuth().getPassword()).isEqualTo(\"some password\");\n  }\n\n  @Test\n  public void testToCredHelperClosure() {\n    assertThat(testJibExtension.getTo().getImage()).isNull();\n    assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isNull();\n\n    testJibExtension.to(\n        to -> {\n          to.setImage(\"some image\");\n          to.credHelper(\n              credHelper -> {\n                credHelper.setHelper(\"some cred helper\");\n                credHelper.setEnvironment(Collections.singletonMap(\"ENV_VARIABLE\", \"Value\"));\n              });\n        });\n    assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isEqualTo(\"some cred helper\");\n    assertThat(testJibExtension.getTo().getCredHelper().getEnvironment())\n        .isEqualTo(Collections.singletonMap(\"ENV_VARIABLE\", \"Value\"));\n  }\n\n  @Test\n  public void testToTags_noTagsPropertySet() {\n    assertThat(testJibExtension.getTo().getTags()).isEmpty();\n  }\n\n  @Test\n  public void testToTags_containsNullTag() {\n    TargetImageParameters testToParameters = generateTargetImageParametersWithTags(null, \"tag1\");\n    Exception exception =\n        assertThrows(IllegalArgumentException.class, () -> testToParameters.getTags());\n    assertThat(exception).hasMessageThat().isEqualTo(\"jib.to.tags contains null tag\");\n  }\n\n  @Test\n  public void testToTags_containsEmptyTag() {\n    TargetImageParameters testToParameters = generateTargetImageParametersWithTags(\"\", \"tag1\");\n    Exception exception =\n        assertThrows(IllegalArgumentException.class, () -> testToParameters.getTags());\n    assertThat(exception).hasMessageThat().isEqualTo(\"jib.to.tags contains empty tag\");\n  }\n\n  @Test\n  public void testContainer() {\n    assertThat(testJibExtension.getContainer().getJvmFlags()).isEmpty();\n    assertThat(testJibExtension.getContainer().getEnvironment()).isEmpty();\n    assertThat(testJibExtension.getContainer().getExtraClasspath()).isEmpty();\n    assertThat(testJibExtension.getContainer().getExpandClasspathDependencies()).isFalse();\n    assertThat(testJibExtension.getContainer().getMainClass()).isNull();\n    assertThat(testJibExtension.getContainer().getArgs()).isNull();\n    assertThat(testJibExtension.getContainer().getFormat()).isSameInstanceAs(ImageFormat.Docker);\n    assertThat(testJibExtension.getContainer().getPorts()).isEmpty();\n    assertThat(testJibExtension.getContainer().getLabels().get()).isEmpty();\n    assertThat(testJibExtension.getContainer().getAppRoot()).isEmpty();\n    assertThat(testJibExtension.getContainer().getFilesModificationTime().get())\n        .isEqualTo(\"EPOCH_PLUS_SECOND\");\n    assertThat(testJibExtension.getContainer().getCreationTime().get()).isEqualTo(\"EPOCH\");\n\n    testJibExtension.container(\n        container -> {\n          container.setJvmFlags(Arrays.asList(\"jvmFlag1\", \"jvmFlag2\"));\n          container.setEnvironment(ImmutableMap.of(\"var1\", \"value1\", \"var2\", \"value2\"));\n          container.setEntrypoint(Arrays.asList(\"foo\", \"bar\", \"baz\"));\n          container.setExtraClasspath(Arrays.asList(\"/d1\", \"/d2\", \"/d3\"));\n          container.setExpandClasspathDependencies(true);\n          container.setMainClass(\"mainClass\");\n          container.setArgs(Arrays.asList(\"arg1\", \"arg2\", \"arg3\"));\n          container.setPorts(Arrays.asList(\"1000\", \"2000-2010\", \"3000\"));\n          container.setFormat(ImageFormat.OCI);\n          container.setAppRoot(\"some invalid appRoot value\");\n          container.getFilesModificationTime().set(\"some invalid time value\");\n          container.getCreationTime().set(\"some other invalid time value\");\n        });\n    ContainerParameters container = testJibExtension.getContainer();\n    assertThat(container.getEntrypoint()).containsExactly(\"foo\", \"bar\", \"baz\").inOrder();\n    assertThat(container.getJvmFlags()).containsExactly(\"jvmFlag1\", \"jvmFlag2\").inOrder();\n    assertThat(container.getEnvironment())\n        .containsExactly(\"var1\", \"value1\", \"var2\", \"value2\")\n        .inOrder();\n    assertThat(container.getExtraClasspath()).containsExactly(\"/d1\", \"/d2\", \"/d3\").inOrder();\n    assertThat(testJibExtension.getContainer().getExpandClasspathDependencies()).isTrue();\n    assertThat(testJibExtension.getContainer().getMainClass()).isEqualTo(\"mainClass\");\n    assertThat(container.getArgs()).containsExactly(\"arg1\", \"arg2\", \"arg3\").inOrder();\n    assertThat(container.getPorts()).containsExactly(\"1000\", \"2000-2010\", \"3000\").inOrder();\n    assertThat(container.getFormat()).isSameInstanceAs(ImageFormat.OCI);\n    assertThat(container.getAppRoot()).isEqualTo(\"some invalid appRoot value\");\n    assertThat(container.getFilesModificationTime().get()).isEqualTo(\"some invalid time value\");\n    assertThat(container.getCreationTime().get()).isEqualTo(\"some other invalid time value\");\n    testJibExtension.container(\n        extensionContainer -> {\n          extensionContainer.getFilesModificationTime().set((String) null);\n          extensionContainer.getCreationTime().set((String) null);\n        });\n    container = testJibExtension.getContainer();\n    assertThat(container.getFilesModificationTime().get()).isEqualTo(\"EPOCH_PLUS_SECOND\");\n    assertThat(container.getCreationTime().get()).isEqualTo(\"EPOCH\");\n  }\n\n  @Test\n  public void testSetFormat() {\n    testJibExtension.container(\n        container -> {\n          container.setFormat(\"OCI\");\n        });\n    ContainerParameters container = testJibExtension.getContainer();\n    assertThat(container.getFormat()).isSameInstanceAs(ImageFormat.OCI);\n  }\n\n  @Test\n  public void testContainerizingMode() {\n    assertThat(testJibExtension.getContainerizingMode()).isEqualTo(\"exploded\");\n  }\n\n  @Test\n  public void testConfigurationName() {\n    assertThat(testJibExtension.getConfigurationName().get()).isEqualTo(\"runtimeClasspath\");\n  }\n\n  @Test\n  public void testExtraDirectories_default() {\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"src/main/jib\"));\n    assertThat(testJibExtension.getExtraDirectories().getPermissions().get()).isEmpty();\n  }\n\n  @Test\n  public void testExtraDirectories() {\n    testJibExtension.extraDirectories(\n        extraDirectories -> {\n          extraDirectories.setPaths(\"test/path\");\n        });\n\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n  }\n\n  @Test\n  public void testExtraDirectories_lazyEvaluation_setFromInto() {\n    testJibExtension.extraDirectories(\n        extraDirectories ->\n            extraDirectories.paths(\n                paths -> {\n                  ProviderFactory providerFactory = fakeProject.getProviders();\n                  Provider<Object> from = providerFactory.provider(() -> \"test/path\");\n                  Provider<String> into = providerFactory.provider(() -> \"/target\");\n                  paths.path(\n                      path -> {\n                        path.setFrom(from);\n                        path.setInto(into);\n                      });\n                }));\n\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getInto())\n        .isEqualTo(\"/target\");\n  }\n\n  @Test\n  public void testExtraDirectories_withTarget() {\n    testJibExtension.extraDirectories(\n        extraDirectories ->\n            extraDirectories.paths(\n                paths -> {\n                  paths.path(\n                      path -> {\n                        path.setFrom(\"test/path\");\n                        path.setInto(\"/\");\n                      });\n                  paths.path(\n                      path -> {\n                        path.setFrom(\"another/path\");\n                        path.setInto(\"/non/default/target\");\n                      });\n                }));\n\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getInto()).isEqualTo(\"/\");\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"another/path\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getInto())\n        .isEqualTo(\"/non/default/target\");\n  }\n\n  @Test\n  public void testExtraDirectories_fileForPaths() {\n    testJibExtension.extraDirectories(\n        extraDirectories -> extraDirectories.setPaths(Paths.get(\"test/path\").toFile()));\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n  }\n\n  @Test\n  public void testExtraDirectories_stringListForPaths() {\n    testJibExtension.extraDirectories(\n        extraDirectories -> extraDirectories.setPaths(Arrays.asList(\"test/path\", \"another/path\")));\n\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"another/path\"));\n  }\n\n  @Test\n  public void testExtraDirectories_lazyEvaluation_StringListForPaths() {\n    testJibExtension.extraDirectories(\n        extraDirectories -> {\n          ProviderFactory providerFactory = fakeProject.getProviders();\n          Provider<Object> paths =\n              providerFactory.provider(() -> Arrays.asList(\"test/path\", \"another/path\"));\n          extraDirectories.setPaths(paths);\n        });\n\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"another/path\"));\n  }\n\n  @Test\n  public void testExtraDirectories_fileListForPaths() {\n    testJibExtension.extraDirectories(\n        extraDirectories ->\n            extraDirectories.setPaths(\n                Arrays.asList(\n                    Paths.get(\"test\", \"path\").toFile(), Paths.get(\"another\", \"path\").toFile())));\n\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"test/path\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"another/path\"));\n  }\n\n  @Test\n  public void testDockerClient() {\n    testJibExtension.dockerClient(\n        dockerClient -> {\n          dockerClient.setExecutable(\"test-executable\");\n          dockerClient.setEnvironment(ImmutableMap.of(\"key1\", \"val1\", \"key2\", \"val2\"));\n        });\n\n    assertThat(testJibExtension.getDockerClient().getExecutablePath())\n        .isEqualTo(Paths.get(\"test-executable\"));\n    assertThat(testJibExtension.getDockerClient().getEnvironment())\n        .containsExactly(\"key1\", \"val1\", \"key2\", \"val2\")\n        .inOrder();\n  }\n\n  @Test\n  public void testOutputFiles() {\n    testJibExtension.outputPaths(\n        outputFiles -> {\n          outputFiles.setDigest(\"/path/to/digest\");\n          outputFiles.setImageId(\"/path/to/id\");\n          outputFiles.setTar(\"path/to/tar\");\n        });\n\n    assertThat(testJibExtension.getOutputPaths().getDigestPath())\n        .isEqualTo(Paths.get(\"/path/to/digest\").toAbsolutePath());\n    assertThat(testJibExtension.getOutputPaths().getImageIdPath())\n        .isEqualTo(Paths.get(\"/path/to/id\").toAbsolutePath());\n    assertThat(testJibExtension.getOutputPaths().getTarPath())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"path/to/tar\"));\n  }\n\n  @Test\n  public void testSkaffold() {\n    testJibExtension.skaffold(\n        skaffold -> {\n          skaffold.sync(sync -> sync.setExcludes(fakeProject.files(\"sync1\", \"sync2\")));\n          skaffold.watch(\n              watch -> {\n                watch.setBuildIncludes(ImmutableList.of(\"watch1\", \"watch2\"));\n                watch.setIncludes(\"watch3\");\n                watch.setExcludes(ImmutableList.of(new File(\"watch4\")));\n              });\n        });\n    Path root = fakeProject.getRootDir().toPath();\n    assertThat(testJibExtension.getSkaffold().getSync().getExcludes())\n        .containsExactly(\n            root.resolve(\"sync1\").toAbsolutePath(), root.resolve(\"sync2\").toAbsolutePath());\n    assertThat(testJibExtension.getSkaffold().getWatch().getBuildIncludes())\n        .containsExactly(\n            root.resolve(\"watch1\").toAbsolutePath(), root.resolve(\"watch2\").toAbsolutePath());\n    assertThat(testJibExtension.getSkaffold().getWatch().getIncludes())\n        .containsExactly(root.resolve(\"watch3\").toAbsolutePath());\n    assertThat(testJibExtension.getSkaffold().getWatch().getExcludes())\n        .containsExactly(root.resolve(\"watch4\").toAbsolutePath());\n  }\n\n  @Test\n  public void testProperties() {\n    System.setProperties(new Properties());\n\n    System.setProperty(\"jib.from.image\", \"fromImage\");\n    assertThat(testJibExtension.getFrom().getImage()).isEqualTo(\"fromImage\");\n    System.setProperty(\"jib.from.credHelper\", \"credHelper\");\n    assertThat(testJibExtension.getFrom().getCredHelper().getHelper()).isEqualTo(\"credHelper\");\n\n    System.setProperty(\"jib.from.platforms\", \"linux/amd64,darwin/arm64\");\n    List<PlatformParameters> platforms = testJibExtension.getFrom().getPlatforms().get();\n    assertThat(platforms).hasSize(2);\n    assertThat(platforms.get(0).getOs()).isEqualTo(\"linux\");\n    assertThat(platforms.get(0).getArchitecture()).isEqualTo(\"amd64\");\n    assertThat(platforms.get(1).getOs()).isEqualTo(\"darwin\");\n    assertThat(platforms.get(1).getArchitecture()).isEqualTo(\"arm64\");\n\n    System.setProperty(\"jib.to.image\", \"toImage\");\n    assertThat(testJibExtension.getTo().getImage()).isEqualTo(\"toImage\");\n    System.setProperty(\"jib.to.tags\", \"tag1,tag2,tag3\");\n    assertThat(testJibExtension.getTo().getTags()).containsExactly(\"tag1\", \"tag2\", \"tag3\");\n    System.setProperty(\"jib.to.credHelper\", \"credHelper\");\n    assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isEqualTo(\"credHelper\");\n\n    System.setProperty(\"jib.container.appRoot\", \"appRoot\");\n    assertThat(testJibExtension.getContainer().getAppRoot()).isEqualTo(\"appRoot\");\n    System.setProperty(\"jib.container.args\", \"arg1,arg2,arg3\");\n    assertThat(testJibExtension.getContainer().getArgs())\n        .containsExactly(\"arg1\", \"arg2\", \"arg3\")\n        .inOrder();\n    System.setProperty(\"jib.container.entrypoint\", \"entry1,entry2,entry3\");\n    assertThat(testJibExtension.getContainer().getEntrypoint())\n        .containsExactly(\"entry1\", \"entry2\", \"entry3\")\n        .inOrder();\n    System.setProperty(\"jib.container.environment\", \"env1=val1,env2=val2\");\n    assertThat(testJibExtension.getContainer().getEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n    System.setProperty(\"jib.container.extraClasspath\", \"/d1,/d2,/d3\");\n    assertThat(testJibExtension.getContainer().getExtraClasspath())\n        .containsExactly(\"/d1\", \"/d2\", \"/d3\")\n        .inOrder();\n    System.setProperty(\"jib.container.expandClasspathDependencies\", \"true\");\n    assertTrue(testJibExtension.getContainer().getExpandClasspathDependencies());\n    System.setProperty(\"jib.container.format\", \"OCI\");\n    assertThat(testJibExtension.getContainer().getFormat()).isSameInstanceAs(ImageFormat.OCI);\n    System.setProperty(\"jib.container.jvmFlags\", \"flag1,flag2,flag3\");\n    assertThat(testJibExtension.getContainer().getJvmFlags())\n        .containsExactly(\"flag1\", \"flag2\", \"flag3\")\n        .inOrder();\n    System.setProperty(\"jib.container.labels\", \"label1=val1,label2=val2\");\n    assertThat(testJibExtension.getContainer().getLabels().get())\n        .containsExactly(\"label1\", \"val1\", \"label2\", \"val2\")\n        .inOrder();\n    System.setProperty(\"jib.container.mainClass\", \"main\");\n    assertThat(testJibExtension.getContainer().getMainClass()).isEqualTo(\"main\");\n    System.setProperty(\"jib.container.ports\", \"port1,port2,port3\");\n    assertThat(testJibExtension.getContainer().getPorts())\n        .containsExactly(\"port1\", \"port2\", \"port3\")\n        .inOrder();\n    System.setProperty(\"jib.container.user\", \"myUser\");\n    assertThat(testJibExtension.getContainer().getUser()).isEqualTo(\"myUser\");\n    System.setProperty(\"jib.container.filesModificationTime\", \"2011-12-03T22:42:05Z\");\n    testJibExtension\n        .getContainer()\n        .getFilesModificationTime()\n        .set(\"property should override value\");\n    assertThat(testJibExtension.getContainer().getFilesModificationTime().get())\n        .isEqualTo(\"2011-12-03T22:42:05Z\");\n    System.setProperty(\"jib.container.creationTime\", \"2011-12-03T11:42:05Z\");\n    testJibExtension.getContainer().getCreationTime().set(\"property should override value\");\n    assertThat(testJibExtension.getContainer().getCreationTime().get())\n        .isEqualTo(\"2011-12-03T11:42:05Z\");\n    System.setProperty(\"jib.containerizingMode\", \"packaged\");\n    assertThat(testJibExtension.getContainerizingMode()).isEqualTo(\"packaged\");\n\n    System.setProperty(\"jib.extraDirectories.paths\", \"/foo,/bar/baz\");\n    assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2);\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom())\n        .isEqualTo(Paths.get(\"/foo\"));\n    assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom())\n        .isEqualTo(Paths.get(\"/bar/baz\"));\n    System.setProperty(\"jib.extraDirectories.permissions\", \"/foo/bar=707,/baz=456\");\n    assertThat(testJibExtension.getExtraDirectories().getPermissions().get())\n        .containsExactly(\"/foo/bar\", \"707\", \"/baz\", \"456\")\n        .inOrder();\n\n    System.setProperty(\"jib.dockerClient.executable\", \"test-exec\");\n    assertThat(testJibExtension.getDockerClient().getExecutablePath())\n        .isEqualTo(Paths.get(\"test-exec\"));\n    System.setProperty(\"jib.dockerClient.environment\", \"env1=val1,env2=val2\");\n    assertThat(testJibExtension.getDockerClient().getEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n  }\n\n  @Test\n  public void testLazyPropertiesFinalization() {\n    Property<String> filesModificationTime =\n        testJibExtension.getContainer().getFilesModificationTime();\n    filesModificationTime.set((String) null);\n    filesModificationTime.finalizeValue();\n    System.setProperty(\"jib.container.filesModificationTime\", \"EPOCH_PLUS_SECOND\");\n    assertThat(testJibExtension.getContainer().getFilesModificationTime().get())\n        .isEqualTo(\"EPOCH_PLUS_SECOND\");\n    Property<String> creationTime = testJibExtension.getContainer().getCreationTime();\n    creationTime.set((String) null);\n    creationTime.finalizeValue();\n    System.setProperty(\"jib.container.creationTime\", \"EPOCH\");\n    assertThat(testJibExtension.getContainer().getCreationTime().get()).isEqualTo(\"EPOCH\");\n  }\n\n  @Test\n  public void testSystemPropertiesWithInvalidPlatform() {\n    System.setProperty(\"jib.from.platforms\", \"linux /amd64\");\n    assertThrows(IllegalArgumentException.class, testJibExtension.getFrom()::getPlatforms);\n  }\n\n  @Test\n  public void testPropertiesOutputPaths() {\n    System.setProperties(new Properties());\n    // Absolute paths\n    System.setProperty(\"jib.outputPaths.digest\", \"/digest/path\");\n    assertThat(testJibExtension.getOutputPaths().getDigestPath())\n        .isEqualTo(Paths.get(\"/digest/path\").toAbsolutePath());\n    System.setProperty(\"jib.outputPaths.imageId\", \"/id/path\");\n    assertThat(testJibExtension.getOutputPaths().getImageIdPath())\n        .isEqualTo(Paths.get(\"/id/path\").toAbsolutePath());\n    System.setProperty(\"jib.outputPaths.tar\", \"/tar/path\");\n    assertThat(testJibExtension.getOutputPaths().getTarPath())\n        .isEqualTo(Paths.get(\"/tar/path\").toAbsolutePath());\n    // Relative paths\n    System.setProperty(\"jib.outputPaths.digest\", \"digest/path\");\n    assertThat(testJibExtension.getOutputPaths().getDigestPath())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"digest/path\"));\n    System.setProperty(\"jib.outputPaths.imageId\", \"id/path\");\n    assertThat(testJibExtension.getOutputPaths().getImageIdPath())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"id/path\"));\n    System.setProperty(\"jib.outputPaths.tar\", \"tar/path\");\n    assertThat(testJibExtension.getOutputPaths().getTarPath())\n        .isEqualTo(fakeProject.getProjectDir().toPath().resolve(\"tar/path\"));\n    System.setProperty(\"jib.configurationName\", \"myConfiguration\");\n    assertThat(testJibExtension.getConfigurationName().get()).isEqualTo(\"myConfiguration\");\n  }\n\n  private TargetImageParameters generateTargetImageParametersWithTags(String... tags) {\n    HashSet<String> set = new HashSet<>(Arrays.asList(tags));\n    testJibExtension.to(to -> to.setTags(set));\n    return testJibExtension.getTo();\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibPluginTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.truth.Correspondence;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Arrays;\nimport java.util.Set;\nimport org.gradle.api.GradleException;\nimport org.gradle.api.Project;\nimport org.gradle.api.Task;\nimport org.gradle.api.UnknownTaskException;\nimport org.gradle.api.tasks.TaskContainer;\nimport org.gradle.api.tasks.TaskProvider;\nimport org.gradle.jvm.tasks.Jar;\nimport org.gradle.testfixtures.ProjectBuilder;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.GradleRunner;\nimport org.gradle.testkit.runner.UnexpectedBuildFailure;\nimport org.junit.After;\nimport org.junit.Assume;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link JibPlugin}. */\npublic class JibPluginTest {\n\n  private static final ImmutableList<String> KNOWN_JIB_TASKS =\n      ImmutableList.of(\n          JibPlugin.BUILD_IMAGE_TASK_NAME,\n          JibPlugin.BUILD_DOCKER_TASK_NAME,\n          JibPlugin.BUILD_TAR_TASK_NAME);\n\n  private static final Correspondence<Object, Task> PROVIDES_TASK_OF =\n      Correspondence.from(\n          (object, task) ->\n              object instanceof TaskProvider && ((TaskProvider<?>) object).get().equals(task),\n          \"provides task of\");\n\n  private static boolean isJava8Runtime() {\n    return System.getProperty(\"java.version\").startsWith(\"1.8.\");\n  }\n\n  @Rule public final TemporaryFolder testProjectRoot = new TemporaryFolder();\n  @Rule public final TestProject testProject = new TestProject(\"lazy-evaluation\");\n\n  @After\n  public void tearDown() {\n    System.clearProperty(JibPlugin.REQUIRED_VERSION_PROPERTY_NAME);\n  }\n\n  @Test\n  public void testCheckGradleVersion_pass() throws IOException {\n    Assume.assumeTrue(isJava8Runtime());\n\n    // Copy build file to temp dir\n    Path buildFile = testProjectRoot.getRoot().toPath().resolve(\"build.gradle\");\n    InputStream buildFileContent =\n        getClass().getClassLoader().getResourceAsStream(\"gradle/plugin-test/build.gradle\");\n    Files.copy(buildFileContent, buildFile);\n\n    BuildResult result =\n        GradleRunner.create()\n            .withProjectDir(testProjectRoot.getRoot())\n            .withPluginClasspath()\n            .withGradleVersion(JibPlugin.GRADLE_MIN_VERSION.getVersion())\n            .build();\n    assertThat(result).isNotNull();\n  }\n\n  @Test\n  public void testCheckGradleVersion_fail() throws IOException {\n    Assume.assumeTrue(isJava8Runtime());\n\n    // Copy build file to temp dir\n    Path buildFile = testProjectRoot.getRoot().toPath().resolve(\"build.gradle\");\n    InputStream buildFileContent =\n        getClass().getClassLoader().getResourceAsStream(\"gradle/plugin-test/build.gradle\");\n    Files.copy(buildFileContent, buildFile);\n\n    GradleRunner gradleRunner =\n        GradleRunner.create()\n            .withProjectDir(testProjectRoot.getRoot())\n            .withPluginClasspath()\n            .withGradleVersion(\"4.3\");\n\n    Exception exception = assertThrows(UnexpectedBuildFailure.class, () -> gradleRunner.build());\n    assertThat(exception)\n        .hasMessageThat()\n        .contains(\n            \"Detected Gradle 4.3, but jib requires \"\n                + JibPlugin.GRADLE_MIN_VERSION\n                + \" or higher. You can upgrade by running 'gradle wrapper --gradle-version=\"\n                + JibPlugin.GRADLE_MIN_VERSION.getVersion()\n                + \"'.\");\n  }\n\n  @Test\n  public void testCheckJibVersionNames() {\n    // These identifiers will be baked into Skaffold and should not be changed\n    assertThat(JibPlugin.REQUIRED_VERSION_PROPERTY_NAME).isEqualTo(\"jib.requiredVersion\");\n    assertThat(JibPlugin.SKAFFOLD_CHECK_REQUIRED_VERSION_TASK_NAME)\n        .isEqualTo(\"_skaffoldFailIfJibOutOfDate\");\n  }\n\n  @Test\n  public void testCheckJibVersionInvoked() {\n    Project project = createProject();\n    System.setProperty(JibPlugin.REQUIRED_VERSION_PROPERTY_NAME, \"10000.0\"); // not here yet\n\n    Exception exception =\n        assertThrows(\n            GradleException.class,\n            () -> project.getPluginManager().apply(\"com.google.cloud.tools.jib\"));\n    // Gradle tests aren't run from a jar and so don't have an identifiable plugin version\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"Failed to apply plugin 'com.google.cloud.tools.jib'.\");\n    assertThat(exception.getCause())\n        .hasMessageThat()\n        .isEqualTo(\"Could not determine Jib plugin version\");\n  }\n\n  @Test\n  public void testWebAppProject() {\n    Project project = createProject(\"java\", \"war\", \"com.google.cloud.tools.jib\");\n\n    TaskContainer tasks = project.getTasks();\n    Task warTask = tasks.getByPath(\":war\");\n    assertThat(warTask).isNotNull();\n\n    for (String taskName : KNOWN_JIB_TASKS) {\n      Set<Object> taskDependencies = tasks.getByPath(taskName).getDependsOn();\n      assertThat(taskDependencies).comparingElementsUsing(PROVIDES_TASK_OF).contains(warTask);\n    }\n  }\n\n  @Test\n  public void testWebAppProject_bootWar() {\n    Project project =\n        createProject(\"java\", \"war\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n\n    TaskContainer tasks = project.getTasks();\n    Task warTask = tasks.getByPath(\":war\");\n    Task bootWarTask = tasks.getByPath(\":bootWar\");\n    assertThat(warTask).isNotNull();\n    assertThat(bootWarTask).isNotNull();\n\n    for (String taskName : KNOWN_JIB_TASKS) {\n      Set<Object> taskDependencies = tasks.getByPath(taskName).getDependsOn();\n      assertThat(taskDependencies)\n          .comparingElementsUsing(PROVIDES_TASK_OF)\n          .containsAtLeast(warTask, bootWarTask);\n    }\n  }\n\n  @Test\n  public void testWebAppProject_bootWarDisabled() {\n    Project project =\n        createProject(\"java\", \"war\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    TaskContainer tasks = project.getTasks();\n    // should depend on bootWar even if disabled\n    tasks.named(\"bootWar\").configure(task -> task.setEnabled(false));\n\n    Task warTask = tasks.getByPath(\":war\");\n    Task bootWarTask = tasks.getByPath(\":bootWar\");\n    assertThat(warTask).isNotNull();\n    assertThat(bootWarTask).isNotNull();\n\n    for (String taskName : KNOWN_JIB_TASKS) {\n      Set<Object> taskDependencies = tasks.getByPath(taskName).getDependsOn();\n      assertThat(taskDependencies)\n          .comparingElementsUsing(PROVIDES_TASK_OF)\n          .containsAtLeast(warTask, bootWarTask);\n    }\n  }\n\n  @Test\n  public void testSpringBootJarProject_nonPackagedMode() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isFalse();\n    assertThat(jar.getArchiveClassifier().get()).isEmpty();\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(jar.getArchiveClassifier().get()).isEqualTo(\"original\");\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode_jarClassifierSet() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    TaskProvider<Task> jarTask = project.getTasks().named(\"jar\");\n    jarTask.configure(task -> ((Jar) task).getArchiveClassifier().set(\"jar-classifier\"));\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(jar.getArchiveClassifier().get()).isEqualTo(\"jar-classifier\");\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode_bootJarClassifierSet() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    TaskProvider<Task> bootJarTask = project.getTasks().named(\"bootJar\");\n    bootJarTask.configure(task -> ((Jar) task).getArchiveClassifier().set(\"boot-classifier\"));\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(jar.getArchiveClassifier().get()).isEmpty();\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode_jarEnabled() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    project.getTasks().named(\"jar\").configure(task -> task.setEnabled(true));\n\n    TaskContainer tasks = project.getTasks();\n    Exception exception = assertThrows(GradleException.class, () -> tasks.getByPath(\":jar\"));\n    assertThat(exception.getCause())\n        .hasMessageThat()\n        .startsWith(\n            \"Both 'bootJar' and 'jar' tasks are enabled, but they write their jar file into the \"\n                + \"same location at \");\n    assertThat(exception.getCause())\n        .hasMessageThat()\n        .endsWith(\"root.jar. Did you forget to set 'archiveClassifier' on either task?\");\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode_jarEnabledAndClassifierSet() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    TaskProvider<Task> jarTask = project.getTasks().named(\"jar\");\n    jarTask.configure(task -> task.setEnabled(true));\n    jarTask.configure(task -> ((Jar) task).getArchiveClassifier().set(\"jar-classifier\"));\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(jar.getArchiveClassifier().get()).isEqualTo(\"jar-classifier\");\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode_jarEnabledAndBootJarClassifierSet() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    TaskProvider<Task> bootJarTask = project.getTasks().named(\"bootJar\");\n    bootJarTask.configure(task -> ((Jar) task).getArchiveClassifier().set(\"boot-classifier\"));\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(jar.getArchiveClassifier().get()).isEmpty();\n  }\n\n  @Test\n  public void testSpringBootJarProject_packagedMode_jarEnabledAndBootJarDisabled() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    project.getTasks().named(\"jar\").configure(task -> task.setEnabled(true));\n    project.getTasks().named(\"bootJar\").configure(task -> task.setEnabled(false));\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(project.getTasks().getByPath(\":bootJar\").getEnabled()).isFalse();\n    assertThat(jar.getArchiveClassifier().get()).isEmpty();\n  }\n\n  @Test\n  public void\n      testSpringBootJarProject_packagedMode_jarEnabledAndBootJarDisabledAndJarClassifierSet() {\n    Project project =\n        createProject(\"java\", \"org.springframework.boot\", \"com.google.cloud.tools.jib\");\n    JibExtension jibExtension = (JibExtension) project.getExtensions().getByName(\"jib\");\n    jibExtension.setContainerizingMode(\"packaged\");\n    TaskProvider<Task> jarTask = project.getTasks().named(\"jar\");\n    jarTask.configure(task -> task.setEnabled(true));\n    jarTask.configure(task -> ((Jar) task).getArchiveClassifier().set(\"jar-classifier\"));\n    project.getTasks().named(\"bootJar\").configure(task -> task.setEnabled(false));\n\n    Jar jar = (Jar) project.getTasks().getByPath(\":jar\");\n    assertThat(jar.getEnabled()).isTrue();\n    assertThat(project.getTasks().getByPath(\":bootJar\").getEnabled()).isFalse();\n    assertThat(jar.getArchiveClassifier().get()).isEqualTo(\"jar-classifier\");\n  }\n\n  @Test\n  public void testNonWebAppProject() {\n    Project project = createProject(\"java\", \"com.google.cloud.tools.jib\");\n\n    TaskContainer tasks = project.getTasks();\n    Exception exception = assertThrows(UnknownTaskException.class, () -> tasks.getByPath(\":war\"));\n    assertThat(exception).hasMessageThat().isNotNull();\n  }\n\n  @Test\n  public void testJibTaskGroupIsSet() {\n    Project project = createProject(\"java\", \"com.google.cloud.tools.jib\");\n\n    TaskContainer tasks = project.getTasks();\n    KNOWN_JIB_TASKS.forEach(\n        taskName -> assertThat(tasks.getByPath(taskName).getGroup()).isEqualTo(\"Jib\"));\n  }\n\n  @Test\n  public void testLazyEvalForImageAndTags() {\n    UnexpectedBuildFailure exception =\n        assertThrows(\n            UnexpectedBuildFailure.class,\n            () -> testProject.build(JibPlugin.BUILD_IMAGE_TASK_NAME, \"-Djib.console=plain\"));\n\n    String output = exception.getBuildResult().getOutput();\n    assertThat(output)\n        .contains(\n            \"Containerizing application to updated-image, updated-image:updated-tag, updated-image:tag2\");\n  }\n\n  @Test\n  public void testLazyEvalForLabels() {\n    BuildResult showLabels = testProject.build(\"showlabels\", \"-Djib.console=plain\");\n    assertThat(showLabels.getOutput())\n        .contains(\n            \"labels contain values [firstkey:updated-first-label, secondKey:updated-second-label]\");\n  }\n\n  @Test\n  public void testLazyEvalForEntryPoint() {\n    BuildResult showEntrypoint = testProject.build(\"showentrypoint\", \"-Djib.console=plain\");\n    assertThat(showEntrypoint.getOutput()).contains(\"entrypoint contains updated\");\n  }\n\n  @Test\n  public void testLazyEvalForExtraDirectories() {\n    BuildResult checkExtraDirectories =\n        testProject.build(\"check-extra-directories\", \"-Djib.console=plain\");\n    assertThat(checkExtraDirectories.getOutput()).contains(\"[/updated:755]\");\n    assertThat(checkExtraDirectories.getOutput()).contains(\"updated-custom-extra-dir\");\n  }\n\n  @Test\n  public void testLazyEvalForExtraDirectories_individualPaths() throws IOException {\n    BuildResult checkExtraDirectories =\n        testProject.build(\n            \"check-extra-directories\", \"-b=build-extra-dirs.gradle\", \"-Djib.console=plain\");\n\n    Path extraDirectoryPath =\n        testProject\n            .getProjectRoot()\n            .resolve(\"src\")\n            .resolve(\"main\")\n            .resolve(\"updated-custom-extra-dir\")\n            .toRealPath();\n    assertThat(checkExtraDirectories.getOutput())\n        .contains(\"extraDirectories (from): [\" + extraDirectoryPath + \"]\");\n    assertThat(checkExtraDirectories.getOutput())\n        .contains(\"extraDirectories (into): [/updated-custom-into-dir]\");\n    assertThat(checkExtraDirectories.getOutput())\n        .contains(\"extraDirectories (includes): [[include.txt]]\");\n    assertThat(checkExtraDirectories.getOutput())\n        .contains(\"extraDirectories (excludes): [[exclude.txt]]\");\n  }\n\n  @Test\n  public void testLazyEvalForContainerCreationAndFileModificationTimes() {\n    BuildResult showTimes = testProject.build(\"showtimes\", \"-Djib.console=plain\");\n    String output = showTimes.getOutput();\n    assertThat(output).contains(\"creationTime=2022-07-19T10:23:42Z\");\n    assertThat(output).contains(\"filesModificationTime=2022-07-19T11:23:42Z\");\n  }\n\n  @Test\n  public void testLazyEvalForMainClass() {\n    BuildResult showLabels = testProject.build(\"showMainClass\");\n    assertThat(showLabels.getOutput()).contains(\"mainClass value updated\");\n  }\n\n  @Test\n  public void testLazyEvalForJvmFlags() {\n    BuildResult showLabels = testProject.build(\"showJvmFlags\");\n    assertThat(showLabels.getOutput()).contains(\"jvmFlags value [updated]\");\n  }\n\n  private Project createProject(String... plugins) {\n    Project project =\n        ProjectBuilder.builder().withProjectDir(testProjectRoot.getRoot()).withName(\"root\").build();\n    Arrays.asList(plugins).forEach(project.getPluginManager()::apply);\n    return project;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/TaskCommonTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.plugins.common.ProjectProperties;\nimport com.google.common.util.concurrent.Futures;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport org.gradle.api.Project;\nimport org.gradle.api.Task;\nimport org.gradle.api.plugins.JavaPlugin;\nimport org.gradle.api.plugins.WarPlugin;\nimport org.gradle.api.tasks.TaskProvider;\nimport org.gradle.api.tasks.bundling.War;\nimport org.gradle.testfixtures.ProjectBuilder;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\nimport org.springframework.boot.gradle.plugin.SpringBootPlugin;\nimport org.springframework.boot.gradle.tasks.bundling.BootWar;\n\n/** Tests for {@link TaskCommon}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class TaskCommonTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n  @Mock private ProjectProperties mockProjectProperties;\n\n  @Before\n  public void setUp() {\n    System.clearProperty(\"jib.extraDirectories.paths\");\n    System.clearProperty(\"jib.extraDirectories.permissions\");\n  }\n\n  @Test\n  public void testGetWarTask_normalJavaProject() {\n    Project project = ProjectBuilder.builder().build();\n    project.getPlugins().apply(JavaPlugin.class);\n\n    TaskProvider<Task> warProviderTask = TaskCommon.getWarTaskProvider(project);\n    assertThat(warProviderTask).isNull();\n  }\n\n  @Test\n  public void testGetWarTask_normalWarProject() {\n    Project project = ProjectBuilder.builder().build();\n    project.getPlugins().apply(WarPlugin.class);\n\n    TaskProvider<Task> warTask = TaskCommon.getWarTaskProvider(project);\n    assertThat(warTask).isNotNull();\n    assertThat(warTask.get()).isInstanceOf(War.class);\n  }\n\n  @Test\n  public void testGetBootWarTask_bootWarProject() {\n    Project project = ProjectBuilder.builder().build();\n    project.getPlugins().apply(WarPlugin.class);\n    project.getPlugins().apply(SpringBootPlugin.class);\n\n    TaskProvider<Task> bootWarTask = TaskCommon.getBootWarTaskProvider(project);\n    assertThat(bootWarTask).isNotNull();\n    assertThat(bootWarTask.get()).isInstanceOf(BootWar.class);\n  }\n\n  @Test\n  public void testFinishUpdateChecker_correctMessageLogged() {\n    when(mockProjectProperties.getToolName()).thenReturn(\"tool-name\");\n    when(mockProjectProperties.getToolVersion()).thenReturn(\"2.0.0\");\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.of(\"2.1.0\"));\n    TaskCommon.finishUpdateChecker(mockProjectProperties, updateCheckFuture);\n\n    verify(mockProjectProperties)\n        .log(\n            LogEvent.lifecycle(\n                \"\\n\\u001B[33mA new version of tool-name (2.1.0) is available (currently using 2.0.0). \"\n                    + \"Update your build configuration to use the latest features and fixes!\\n\"\n                    + ProjectInfo.GITHUB_URL\n                    + \"/blob/master/jib-gradle-plugin/CHANGELOG.md\\u001B[0m\\n\\n\"\n                    + \"Please see \"\n                    + ProjectInfo.GITHUB_URL\n                    + \"/blob/master/docs/privacy.md for info on disabling this update check.\\n\"));\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/TestProject.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle;\n\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.io.Resources;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.GradleRunner;\nimport org.junit.rules.TemporaryFolder;\n\n// TODO: Consolidate with TestProject in jib-maven-plugin.\n/** Works with the test Gradle projects in the {@code resources/projects} directory. */\npublic class TestProject extends TemporaryFolder implements Closeable {\n\n  private static final String PROJECTS_PATH_IN_RESOURCES = \"gradle/projects/\";\n\n  /** Copies test project {@code projectName} to {@code destination} folder. */\n  private static void copyProject(String projectName, Path destination)\n      throws IOException, URISyntaxException {\n    Path projectPathInResources =\n        Paths.get(Resources.getResource(PROJECTS_PATH_IN_RESOURCES + projectName).toURI());\n    new DirectoryWalker(projectPathInResources)\n        .filterRoot()\n        .walk(\n            path -> {\n              // Creates the same path in the destDir.\n              Path destPath = destination.resolve(projectPathInResources.relativize(path));\n              if (Files.isDirectory(path)) {\n                Files.createDirectory(destPath);\n              } else {\n                Files.copy(path, destPath);\n              }\n            });\n  }\n\n  private final String testProjectName;\n  private String gradleVersion = JibPlugin.GRADLE_MIN_VERSION.getVersion();\n  private GradleRunner gradleRunner;\n\n  private Path projectRoot;\n\n  /** Initialize with a specific project directory. */\n  public TestProject(String testProjectName) {\n    this.testProjectName = testProjectName;\n  }\n\n  @Override\n  public void close() {\n    after();\n  }\n\n  @Override\n  protected void before() throws Throwable {\n    super.before();\n\n    projectRoot = newFolder().toPath();\n    copyProject(testProjectName, projectRoot);\n\n    gradleRunner =\n        GradleRunner.create()\n            .withGradleVersion(gradleVersion)\n            .withProjectDir(projectRoot.toFile())\n            .withPluginClasspath();\n  }\n\n  public TestProject withGradleVersion(String version) {\n    gradleVersion = version;\n    return this;\n  }\n\n  public BuildResult build(String... gradleArguments) {\n    return gradleRunner.withArguments(gradleArguments).build();\n  }\n\n  public BuildResult build(List<String> gradleArguments) {\n    return gradleRunner.withArguments(gradleArguments).build();\n  }\n\n  public Path getProjectRoot() {\n    return projectRoot;\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2Test.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.gradle.JibPlugin;\nimport com.google.cloud.tools.jib.gradle.TestProject;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldFilesOutput;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.BuildTask;\nimport org.gradle.testkit.runner.TaskOutcome;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link FilesTaskV2}. */\npublic class FilesTaskV2Test {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule\n  public static final TestProject skaffoldTestProject = new TestProject(\"skaffold-config\");\n\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi-service\");\n\n  @ClassRule\n  public static final TestProject platformProject =\n      new TestProject(\"platform\").withGradleVersion(\"5.2\");\n\n  /**\n   * Verifies that the files task succeeded and returns the list of paths it prints out.\n   *\n   * @param project the project to run the task on\n   * @param moduleName the name of the sub-project, or {@code null} if no sub-project\n   * @return the JSON string printed by the task\n   */\n  private static String verifyTaskSuccess(TestProject project, @Nullable String moduleName) {\n    String taskName =\n        \":\" + (moduleName == null ? \"\" : moduleName + \":\") + JibPlugin.SKAFFOLD_FILES_TASK_V2_NAME;\n    BuildResult buildResult = project.build(taskName, \"-q\", \"-D_TARGET_IMAGE=ignored\");\n    BuildTask jibTask = buildResult.task(taskName);\n    Assert.assertNotNull(jibTask);\n    Assert.assertEquals(TaskOutcome.SUCCESS, jibTask.getOutcome());\n    String output = buildResult.getOutput().trim();\n    MatcherAssert.assertThat(output, CoreMatchers.startsWith(\"BEGIN JIB JSON\"));\n\n    // Return task output with header removed\n    return output.replace(\"BEGIN JIB JSON\", \"\").trim();\n  }\n\n  /**\n   * Asserts that two lists contain the same paths. Required to avoid Mac's /var/ vs. /private/var/\n   * symlink issue.\n   *\n   * @param expected the expected list of paths\n   * @param actual the actual list of paths\n   * @throws IOException if checking if two files are the same fails\n   */\n  private static void assertPathListsAreEqual(List<Path> expected, List<String> actual)\n      throws IOException {\n    Assert.assertEquals(expected.size(), actual.size());\n    for (int index = 0; index < expected.size(); index++) {\n      Assert.assertEquals(\n          expected.get(index).toRealPath(), Paths.get(actual.get(index)).toRealPath());\n    }\n  }\n\n  @Test\n  public void testFilesTask_singleProject() throws IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n    SkaffoldFilesOutput result =\n        new SkaffoldFilesOutput(verifyTaskSuccess(simpleTestProject, null));\n    assertPathListsAreEqual(\n        ImmutableList.of(projectRoot.resolve(\"build.gradle\")), result.getBuild());\n    assertPathListsAreEqual(\n        ImmutableList.of(\n            projectRoot.resolve(\"src/main/resources\"),\n            projectRoot.resolve(\"src/main/java\"),\n            projectRoot.resolve(\"src/main/custom-extra-dir\")),\n        result.getInputs());\n    assertThat(result.getIgnore()).isEmpty();\n  }\n\n  @Test\n  public void testFilesTask_multiProjectSimpleService() throws IOException {\n    Path projectRoot = multiTestProject.getProjectRoot();\n    Path simpleServiceRoot = projectRoot.resolve(\"simple-service\");\n    SkaffoldFilesOutput result =\n        new SkaffoldFilesOutput(verifyTaskSuccess(multiTestProject, \"simple-service\"));\n    assertPathListsAreEqual(\n        ImmutableList.of(\n            projectRoot.resolve(\"build.gradle\"),\n            projectRoot.resolve(\"settings.gradle\"),\n            projectRoot.resolve(\"gradle.properties\"),\n            simpleServiceRoot.resolve(\"build.gradle\")),\n        result.getBuild());\n    assertPathListsAreEqual(\n        ImmutableList.of(simpleServiceRoot.resolve(\"src/main/java\")), result.getInputs());\n    assertThat(result.getIgnore()).isEmpty();\n  }\n\n  @Test\n  public void testFilesTask_multiProjectComplexService() throws IOException {\n    Path projectRoot = multiTestProject.getProjectRoot();\n    Path complexServiceRoot = projectRoot.resolve(\"complex-service\");\n    Path libRoot = projectRoot.resolve(\"lib\");\n    SkaffoldFilesOutput result =\n        new SkaffoldFilesOutput(verifyTaskSuccess(multiTestProject, \"complex-service\"));\n    assertPathListsAreEqual(\n        ImmutableList.of(\n            projectRoot.resolve(\"build.gradle\"),\n            projectRoot.resolve(\"settings.gradle\"),\n            projectRoot.resolve(\"gradle.properties\"),\n            complexServiceRoot.resolve(\"build.gradle\"),\n            libRoot.resolve(\"build.gradle\")),\n        result.getBuild());\n    assertPathListsAreEqual(\n        ImmutableList.of(\n            complexServiceRoot.resolve(\"src/main/extra-resources-1\"),\n            complexServiceRoot.resolve(\"src/main/extra-resources-2\"),\n            complexServiceRoot.resolve(\"src/main/java\"),\n            complexServiceRoot.resolve(\"src/main/other-jib\"),\n            libRoot.resolve(\"src/main/resources\"),\n            libRoot.resolve(\"src/main/java\"),\n            complexServiceRoot.resolve(\n                \"local-m2-repo/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.jar\")),\n        result.getInputs());\n    assertThat(result.getIgnore()).isEmpty();\n  }\n\n  @Test\n  public void testFilesTask_platformProject() throws IOException {\n    Path projectRoot = platformProject.getProjectRoot();\n    Path platformRoot = projectRoot.resolve(\"platform\");\n    Path serviceRoot = projectRoot.resolve(\"service\");\n    SkaffoldFilesOutput result =\n        new SkaffoldFilesOutput(verifyTaskSuccess(platformProject, \"service\"));\n    assertPathListsAreEqual(\n        ImmutableList.of(\n            projectRoot.resolve(\"build.gradle\"),\n            projectRoot.resolve(\"settings.gradle\"),\n            serviceRoot.resolve(\"build.gradle\"),\n            platformRoot.resolve(\"build.gradle\")),\n        result.getBuild());\n    assertPathListsAreEqual(\n        ImmutableList.of(serviceRoot.resolve(\"src/main/java\")), result.getInputs());\n    assertThat(result.getIgnore()).isEmpty();\n  }\n\n  @Test\n  public void testFilesTast_withConfigModifiers() throws IOException {\n    Path projectRoot = skaffoldTestProject.getProjectRoot();\n    SkaffoldFilesOutput result =\n        new SkaffoldFilesOutput(verifyTaskSuccess(skaffoldTestProject, null));\n    assertPathListsAreEqual(\n        ImmutableList.of(projectRoot.resolve(\"build.gradle\"), projectRoot.resolve(\"script.gradle\")),\n        result.getBuild());\n    assertPathListsAreEqual(\n        ImmutableList.of(\n            projectRoot.resolve(\"src/main/resources\"),\n            projectRoot.resolve(\"src/main/java\"),\n            projectRoot.resolve(\"src/main/jib\"),\n            projectRoot.resolve(\"other/file.txt\")),\n        result.getInputs());\n    assertPathListsAreEqual(\n        ImmutableList.of(projectRoot.resolve(\"src/main/jib/bar\")), result.getIgnore());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/InitTaskTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.cloud.tools.jib.gradle.JibPlugin;\nimport com.google.cloud.tools.jib.gradle.TestProject;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldInitOutput;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.BuildTask;\nimport org.gradle.testkit.runner.TaskOutcome;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link InitTask}. */\npublic class InitTaskTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi-service\");\n\n  /**\n   * Verifies that the files task succeeded and returns the list of JSON strings printed by the\n   * task.\n   *\n   * @param project the project to run the task on\n   * @return the JSON strings printed by the task\n   */\n  private static List<String> getJsons(TestProject project) {\n    BuildResult buildResult =\n        project.build(JibPlugin.SKAFFOLD_INIT_TASK_NAME, \"-q\", \"-D_TARGET_IMAGE=testimage\");\n    BuildTask jibTask = buildResult.task(\":\" + JibPlugin.SKAFFOLD_INIT_TASK_NAME);\n    Assert.assertNotNull(jibTask);\n    Assert.assertEquals(TaskOutcome.SUCCESS, jibTask.getOutcome());\n    String output = buildResult.getOutput().trim();\n    MatcherAssert.assertThat(output, CoreMatchers.startsWith(\"BEGIN JIB JSON\"));\n\n    Pattern pattern = Pattern.compile(\"BEGIN JIB JSON\\r?\\n(\\\\{.*})\");\n    Matcher matcher = pattern.matcher(output);\n    List<String> jsons = new ArrayList<>();\n    while (matcher.find()) {\n      jsons.add(matcher.group(1));\n    }\n\n    // Return task output with header removed\n    return jsons;\n  }\n\n  @Test\n  public void testFilesTask_singleProject() throws IOException {\n    List<String> outputs = getJsons(simpleTestProject);\n    Assert.assertEquals(1, outputs.size());\n\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(0));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertNull(skaffoldInitOutput.getProject());\n  }\n\n  @Test\n  public void testFilesTask_multiProject() throws IOException {\n    List<String> outputs = getJsons(multiTestProject);\n    Assert.assertEquals(2, outputs.size());\n\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(0));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"complex-service\", skaffoldInitOutput.getProject());\n\n    skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(1));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"simple-service\", skaffoldInitOutput.getProject());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/skaffold/SyncMapTaskTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.skaffold;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.gradle.JibPlugin;\nimport com.google.cloud.tools.jib.gradle.TestProject;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldSyncMapTemplate;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldSyncMapTemplate.FileTemplate;\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.gradle.testkit.runner.BuildResult;\nimport org.gradle.testkit.runner.BuildTask;\nimport org.gradle.testkit.runner.TaskOutcome;\nimport org.gradle.testkit.runner.UnexpectedBuildFailure;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link SyncMapTask}. */\npublic class SyncMapTaskTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n  @ClassRule public static final TestProject skaffoldProject = new TestProject(\"skaffold-config\");\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi-service\");\n  @ClassRule public static final TestProject warProject = new TestProject(\"war_servlet25\");\n\n  /**\n   * Verifies that the sync map task succeeded and returns the parsed json.\n   *\n   * @param project the project to run the task on\n   * @param moduleName the name of the sub-project, or {@code null} if no sub-project\n   * @param params extra gradle cli params to use during the build\n   * @return the list of paths printed by the task\n   * @throws IOException if the json parser fails\n   */\n  private static SkaffoldSyncMapTemplate generateTemplate(\n      TestProject project, @Nullable String moduleName, @Nullable List<String> params)\n      throws IOException {\n    String taskName =\n        \":\" + (moduleName == null ? \"\" : moduleName + \":\") + JibPlugin.SKAFFOLD_SYNC_MAP_TASK_NAME;\n    List<String> buildParams = new ArrayList<>();\n    buildParams.add(taskName);\n    buildParams.add(\"-q\");\n    buildParams.add(\"-D_TARGET_IMAGE=ignored\");\n    buildParams.add(\"--stacktrace\");\n    if (params != null) {\n      buildParams.addAll(params);\n    }\n    BuildResult buildResult = project.build(buildParams);\n    BuildTask jibTask = buildResult.task(taskName);\n    Assert.assertNotNull(jibTask);\n    Assert.assertEquals(TaskOutcome.SUCCESS, jibTask.getOutcome());\n\n    List<String> outputLines =\n        Splitter.on(System.lineSeparator()).omitEmptyStrings().splitToList(buildResult.getOutput());\n    Assert.assertEquals(2, outputLines.size());\n    Assert.assertEquals(\"BEGIN JIB JSON: SYNCMAP/1\", outputLines.get(0));\n    return SkaffoldSyncMapTemplate.from(outputLines.get(1));\n  }\n\n  private static void assertFilePaths(Path src, AbsoluteUnixPath dest, FileTemplate template)\n      throws IOException {\n    Assert.assertEquals(src.toRealPath().toString(), template.getSrc());\n    Assert.assertEquals(dest.toString(), template.getDest());\n  }\n\n  @Test\n  public void testSyncMapTask_singleProject() throws IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n    SkaffoldSyncMapTemplate parsed = generateTemplate(simpleTestProject, null, null);\n\n    List<FileTemplate> generated = parsed.getGenerated();\n    Assert.assertEquals(2, generated.size());\n    assertFilePaths(\n        projectRoot.resolve(\"build/resources/main/world\"),\n        AbsoluteUnixPath.get(\"/app/resources/world\"),\n        generated.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"build/classes/java/main/com/test/HelloWorld.class\"),\n        AbsoluteUnixPath.get(\"/app/classes/com/test/HelloWorld.class\"),\n        generated.get(1));\n\n    List<FileTemplate> direct = parsed.getDirect();\n    Assert.assertEquals(2, direct.size());\n    assertFilePaths(\n        projectRoot.resolve(\"src/main/custom-extra-dir/bar/cat\"),\n        AbsoluteUnixPath.get(\"/bar/cat\"),\n        direct.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"src/main/custom-extra-dir/foo\"),\n        AbsoluteUnixPath.get(\"/foo\"),\n        direct.get(1));\n  }\n\n  @Test\n  public void testSyncMapTask_multiProjectOutput() throws IOException {\n    Path projectRoot = multiTestProject.getProjectRoot();\n    Path complexServiceRoot = projectRoot.resolve(\"complex-service\");\n    Path libRoot = projectRoot.resolve(\"lib\");\n    SkaffoldSyncMapTemplate parsed = generateTemplate(multiTestProject, \"complex-service\", null);\n\n    List<FileTemplate> generated = parsed.getGenerated();\n    Assert.assertEquals(4, generated.size());\n    assertFilePaths(\n        libRoot.resolve(\"build/libs/lib.jar\"),\n        AbsoluteUnixPath.get(\"/app/libs/lib.jar\"),\n        generated.get(0));\n    assertFilePaths(\n        complexServiceRoot.resolve(\"build/resources/main/resource1.txt\"),\n        AbsoluteUnixPath.get(\"/app/resources/resource1.txt\"),\n        generated.get(1));\n    assertFilePaths(\n        complexServiceRoot.resolve(\"build/resources/main/resource2.txt\"),\n        AbsoluteUnixPath.get(\"/app/resources/resource2.txt\"),\n        generated.get(2));\n    assertFilePaths(\n        complexServiceRoot.resolve(\"build/classes/java/main/com/test/HelloWorld.class\"),\n        AbsoluteUnixPath.get(\"/app/classes/com/test/HelloWorld.class\"),\n        generated.get(3));\n\n    List<FileTemplate> direct = parsed.getDirect();\n    Assert.assertEquals(2, direct.size());\n    assertFilePaths(\n        complexServiceRoot.resolve(\n            \"local-m2-repo/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.jar\"),\n        AbsoluteUnixPath.get(\"/app/libs/tiny-test-lib-0.0.1-SNAPSHOT.jar\"),\n        direct.get(0));\n    assertFilePaths(\n        complexServiceRoot.resolve(\"src/main/other-jib/extra-file\"),\n        AbsoluteUnixPath.get(\"/extra-file\"),\n        direct.get(1));\n  }\n\n  @Test\n  public void testSyncMapTask_withSkaffoldConfig() throws IOException {\n    Path projectRoot = skaffoldProject.getProjectRoot();\n    SkaffoldSyncMapTemplate parsed = generateTemplate(skaffoldProject, null, null);\n\n    List<FileTemplate> generated = parsed.getGenerated();\n    Assert.assertEquals(2, generated.size());\n    assertFilePaths(\n        projectRoot.resolve(\"build/resources/main/world\"),\n        AbsoluteUnixPath.get(\"/app/resources/world\"),\n        generated.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"build/classes/java/main/com/test2/GoodbyeWorld.class\"),\n        AbsoluteUnixPath.get(\"/app/classes/com/test2/GoodbyeWorld.class\"),\n        generated.get(1));\n    // classes/java/main/com/test is ignored\n\n    List<FileTemplate> direct = parsed.getDirect();\n    Assert.assertEquals(1, direct.size());\n    assertFilePaths(\n        projectRoot.resolve(\"src/main/jib/bar/cat\"),\n        AbsoluteUnixPath.get(\"/bar/cat\"),\n        direct.get(0));\n    // src/main/custom-extra-dir/foo is ignored\n  }\n\n  @Test\n  public void testSyncMapTask_failIfWar() throws IOException {\n    Path projectRoot = warProject.getProjectRoot();\n    try {\n      generateTemplate(warProject, null, null);\n      Assert.fail();\n    } catch (UnexpectedBuildFailure ex) {\n      Assert.assertTrue(\n          ex.getMessage()\n              .contains(\n                  \"org.gradle.api.GradleException: Skaffold sync is currently only available for 'jar' style Jib projects, but the project \"\n                      + projectRoot.getFileName()\n                      + \" is configured to generate a 'war'\"));\n    }\n  }\n\n  @Test\n  public void testSyncMapTask_failIfJarContainerizationMode() throws IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n    try {\n      generateTemplate(\n          simpleTestProject, null, ImmutableList.of(\"-Djib.containerizingMode=packaged\"));\n      Assert.fail();\n    } catch (UnexpectedBuildFailure ex) {\n      Assert.assertTrue(\n          ex.getMessage()\n              .contains(\n                  \"Skaffold sync is currently only available for Jib projects in 'exploded' containerizing mode, but the containerizing mode of \"\n                      + projectRoot.getFileName()\n                      + \" is 'packaged'\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/build/resources/main/resourceA",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/build/resources/main/resourceB",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/build/resources/main/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/dependencies/library.jarC.jar",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/dependencies/libraryA.jar",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/dependencies/libraryB.jar",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/dependencies/more/dependency-1.0.0.jar",
    "content": "]e$\u000fỀx\u0017,\u001b\n.3I݅3\u001c8V\u0014KA\u0003M\u0007)=5~'qю$[-\t:&% \u001cEo\u00067Ns`iZ\u00040\u0019MT.9J[}?\u000f\\E\f\u0019}UvJd\u000eo(i\u0019\"Mԛ_+/\u001dcI<Zje44%\u001fd2\u000e?l>.-\bO=\u0013Hi"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/application/extra-directory/foo",
    "content": "Unused file for committing extra directory"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/plugin-test/build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/build.gradle",
    "content": "// this file doesn't do anything"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  flatDir {\n    dirs 'libs'\n  }\n}\n\njib {\n  from {\n    image = 'scratch'\n  }\n  extraDirectories {\n    paths = file('src/main/other-jib')\n  }\n}\n\nsourceSets {\n  main {\n    resources {\n      srcDirs 'src/main/extra-resources-1', 'src/main/extra-resources-2'\n    }\n  }\n}\n\ndependencies {\n  implementation project(':lib')\n  implementation name: 'dependency-1.0.0'\n  implementation name: 'dependencyX-1.0.0-SNAPSHOT'\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/src/main/extra-resources-1/resource1.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/src/main/extra-resources-2/resource2.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/src/main/other-jib/extra-file",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/gradle.properties",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/lib/build.gradle",
    "content": "plugins {\n  id 'java'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/lib/src/main/java/com/lib/Lib.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.lib;\n\n/** Shared Code! */\npublic class Lib {\n\n  public String getThing() {\n    return \"thing\";\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/lib/src/main/resources/hi.txt",
    "content": "hi"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/settings.gradle",
    "content": "include ':simple-service'\ninclude ':complex-service'\ninclude ':lib'"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/simple-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\njib {\n  from {\n    image = 'scratch'\n  }\n    // only use buildTar\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/simple-service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build-extra-dirs.gradle",
    "content": "plugins {\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'com.google.guava:guava:23.6-jre'\n}\n\nproject.ext.value = 'original'\n\nproject.afterEvaluate {\n    project.ext.value = 'updated'\n    project.ext.getCustomIncludes = { -> return ['include.txt'] }\n    project.ext.getCustomExcludes = { -> return ['exclude.txt'] }\n}\n\njib {\n    extraDirectories {\n        paths {\n            path {\n                from = project.provider { 'src/main/' + project.ext.value + '-custom-extra-dir' }\n                into = project.provider { '/' + project.ext.value + '-custom-into-dir' }\n                includes = project.provider { -> project.ext.getCustomIncludes() }\n                excludes = project.provider { -> project.ext.getCustomExcludes() }\n            }\n        }\n    }\n}\n\ntasks.register('check-extra-directories') {\n    List<Object> from = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['from']}\n    List<Object> into = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['into']}\n    List<Object> includes = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['includes'].get()}\n    List<Object> excludes = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['excludes'].get()}\n    println('extraDirectories (from): ' + from)\n    println('extraDirectories (into): ' + into)\n    println('extraDirectories (includes): ' + includes)\n    println('extraDirectories (excludes): ' + excludes)\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'com.google.guava:guava:23.6-jre'\n}\n\nproject.ext.value = 'original'\nproject.ext.jibCreationTime = '1970-01-23T00:23:42Z'\nproject.ext.jibFilesModificationTime = '1970-01-23T01:23:42Z'\n\nproject.afterEvaluate {\n    project.ext.value = 'updated'\n    project.ext.getCustomPermissions = { -> return ['/updated': '755'] }\n    project.ext.jibCreationTime = '2022-07-19T10:23:42Z'\n    project.ext.jibFilesModificationTime = '2022-07-19T11:23:42Z'\n}\n\njib {\n    to {\n        image = project.provider { project.ext.value + '-image' }\n        tags = project.provider { [project.ext.value + '-tag', 'tag2'] }\n    }\n    container {\n        labels = project.provider {\n            [\n                    firstkey : project.ext.value + '-first-label',\n                    secondKey: project.ext.value + '-second-label'\n            ]\n        }\n        entrypoint = project.provider { [project.ext.value] }\n        creationTime = project.provider { project.ext.jibCreationTime }\n        filesModificationTime = project.provider { project.ext.jibFilesModificationTime }\n        mainClass = project.provider { project.ext.value }\n        jvmFlags = project.provider { [project.ext.value] }\n    }\n    extraDirectories {\n        paths = project.provider { ['src/main/' + project.ext.value + '-custom-extra-dir'] }\n        permissions = project.provider { -> project.ext.getCustomPermissions() }\n    }\n}\n\ntasks.register('showlabels') {\n    Map<String, String> prop = project.extensions.getByName('jib')['container']['labels'].get()\n    println('labels contain values ' + prop)\n}\n\ntasks.register('showentrypoint') {\n    List<String> prop = project.extensions.getByName('jib')['container'].getEntrypoint()\n    println('entrypoint contains ' + prop.join(\",\"))\n}\n\ntasks.register('check-extra-directories') {\n    List<Object> paths = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['from']}\n    Map<String, String> permissions = project.extensions.getByName('jib')['extraDirectories']['permissions'].get()\n    println('extraDirectories paths: ' + paths)\n    println('extraDirectories permissions: ' + permissions)\n}\n\ntasks.register('showtimes') {\n    String prop = project.extensions.jib.container.creationTime.get()\n    println('creationTime=' + prop)\n    prop = project.extensions.jib.container.filesModificationTime.get()\n    println('filesModificationTime=' + prop)\n}\n\ntasks.register('showMainClass') {\n    String prop = project.extensions.jib.container.mainClass\n    println('mainClass value ' + prop)\n}\n\ntasks.register('showJvmFlags') {\n    List<String> prop = project.extensions.jib.container.jvmFlags\n    println('jvmFlags value ' + prop)\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\n\npublic class HelloWorld {\n\n  public static void main(String[] args) throws URISyntaxException, IOException {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/src/main/updated-custom-extra-dir/foo",
    "content": "Unused file for committing parent directory so that it exists for extraDirectories tests"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n  // The local repo contains one tiny test library installed with\n  //   mvn org.apache.maven.plugins:maven-install-plugin:2.3.1:install-file \\\n  //       -Dfile=tiny.jar -DlocalRepositoryPath=local-m2-repo/ \\\n  //       -DgroupId=com.google.cloud.tools -DartifactId=tiny-test-lib \\\n  //       -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar\n  maven {\n    url 'file:' + project.projectDir + '/local-m2-repo'\n  }\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n  extraDirectories {\n    paths = file('src/main/other-jib')\n  }\n}\n\nsourceSets {\n  main {\n    resources {\n      srcDirs 'src/main/extra-resources-1', 'src/main/extra-resources-2'\n    }\n  }\n}\n\ndependencies {\n  implementation project(':lib')\n  implementation 'org.apache.commons:commons-io:1.3.2'\n  implementation 'com.google.cloud.tools:tiny-test-lib:0.0.1-SNAPSHOT'\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/local-m2-repo/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata modelVersion=\"1.1.0\">\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>tiny-test-lib</artifactId>\n  <version>0.0.1-SNAPSHOT</version>\n  <versioning>\n    <snapshot>\n      <localCopy>true</localCopy>\n    </snapshot>\n    <lastUpdated>20190911205316</lastUpdated>\n    <snapshotVersions>\n      <snapshotVersion>\n        <extension>jar</extension>\n        <value>0.0.1-SNAPSHOT</value>\n        <updated>20190911205316</updated>\n      </snapshotVersion>\n      <snapshotVersion>\n        <extension>pom</extension>\n        <value>0.0.1-SNAPSHOT</value>\n        <updated>20190911205316</updated>\n      </snapshotVersion>\n    </snapshotVersions>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/local-m2-repo/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.pom",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>tiny-test-lib</artifactId>\n  <version>0.0.1-SNAPSHOT</version>\n  <description>POM was created from install:install-file</description>\n</project>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/local-m2-repo/com/google/cloud/tools/tiny-test-lib/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>tiny-test-lib</artifactId>\n  <versioning>\n    <versions>\n      <version>0.0.1-SNAPSHOT</version>\n    </versions>\n    <lastUpdated>20190911205316</lastUpdated>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/src/main/extra-resources-1/resource1.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/src/main/extra-resources-2/resource2.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/src/main/other-jib/extra-file",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/gradle.properties",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/build.gradle",
    "content": "plugins {\n  id 'java'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/src/main/java/com/lib/Lib.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.lib;\n\n/** Shared Code! */\npublic class Lib {\n\n  public String getThing() {\n    return \"thing\";\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/src/main/resources/hi.txt",
    "content": "hi"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/lib/src/test/java/com/lib/LibTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.lib;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Unit test for simple App. */\npublic class LibTest {\n  /** Rigorous Test :-) */\n  @Test\n  public void testGetThing() {\n    Assert.assertEquals(\"thing\", new Lib().getThing());\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/settings.gradle",
    "content": "include ':simple-service'\ninclude ':complex-service'\ninclude ':lib'"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/platform/build.gradle",
    "content": "subprojects {\n  repositories {\n    mavenCentral()\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/platform/platform/build.gradle",
    "content": "plugins {\n  id 'java-platform'\n}\n\ndependencies {\n  constraints {\n    api 'org.apache.commons:commons-io:1.3.2'\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/platform/service/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ndependencies {\n  implementation platform(project(':platform'))\n\n  implementation 'org.apache.commons:commons-io'\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/platform/service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/platform/settings.gradle",
    "content": "include ':platform'\ninclude ':service'\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/simple/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n  extraDirectories {\n    paths = file('src/main/custom-extra-dir')\n  }\n}\n\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/simple/src/main/custom-extra-dir/bar/cat",
    "content": "cat"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/simple/src/main/custom-extra-dir/foo",
    "content": "foo"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/simple/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\nimport dependency.Greeting;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/** Example class that uses a dependency and a resource file. */\npublic class HelloWorld {\n\n  public static void main(String[] args) throws IOException, URISyntaxException {\n    // 'Greeting' comes from the dependency artfiact.\n    String greeting = Greeting.getGreeting();\n\n    // Gets the contents of the resource file 'world'.\n    ClassLoader classLoader = HelloWorld.class.getClassLoader();\n    Path worldFile = Paths.get(classLoader.getResource(\"world\").toURI());\n    String world = new String(Files.readAllBytes(worldFile), StandardCharsets.UTF_8);\n\n    System.out.println(greeting + \", \" + world + \". \");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/simple/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\ndependencies {\n  implementation files('libs/dependency-1.0.0.jar')\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n  skaffold {\n    watch {\n      buildIncludes = project.file('script.gradle')\n      includes = project.files('other/file.txt')\n      excludes = 'src/main/jib/bar/'\n    }\n    sync {\n      excludes = ['build/classes/java/main/com/test', 'src/main/jib/foo']\n    }\n  }\n}\n\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/other/file.txt",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/script.gradle",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/src/main/java/com/test2/GoodbyeWorld.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test2;\n\npublic class GoodbyeWorld {\n  public static void goodbye() {\n    System.out.println(\"Goodbye, world!\");\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/src/main/jib/bar/cat",
    "content": "cat"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/src/main/jib/foo",
    "content": "foo"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/build.gradle",
    "content": "plugins {\n  id 'java'\n  id 'war'\n  id 'com.google.cloud.tools.jib'\n}\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\nrepositories {\n  mavenCentral()\n}\n\nconfigurations {\n  moreLibs\n}\n\ndependencies {\n  providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0'\n  moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR\n}\n\njib {\n  to {\n    image = System.getProperty('_TARGET_IMAGE')\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom-tomcat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>servlet25</artifactId>\n  <version>1</version>\n  <packaging>war</packaging>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>5.0.0</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>  <!-- random dependency -->\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n      <version>2.1.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>tomcat:8.5-jre8-alpine</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <appRoot>/usr/local/tomcat/webapps/ROOT</appRoot>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>servlet25</artifactId>\n  <version>1</version>\n  <packaging>war</packaging>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>5.0.0</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>  <!-- random dependency -->\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n      <version>2.1.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/extra_js/bogus.js",
    "content": "// nothing inside\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/extra_static/bogus.html",
    "content": "nothing inside\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport jakarta.servlet.http.HttpServlet;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class HelloWorld extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {\n    try {\n      URL worldFile = getServletContext().getResource(\"/WEB-INF/classes/world\");\n      Path path = Paths.get(worldFile.toURI());\n      String world = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);\n\n      response.setContentType(\"text/plain\");\n      response.setCharacterEncoding(\"UTF-8\");\n\n      response.getWriter().print(\"Hello \" + world);\n\n    } catch (URISyntaxException e) {\n      throw new IOException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/webapp/META-INF/MANIFEST.MF",
    "content": "Manifest-Version: 1.0\nClass-Path: \n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Using the old Servlet API 2.5 for demonstration purposes. -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         version=\"2.5\">\n  <servlet>\n    <servlet-name>HelloWorld</servlet-name>\n    <servlet-class>example.HelloWorld</servlet-class>\n  </servlet>\n  <servlet-mapping>\n    <servlet-name>HelloWorld</servlet-name>\n    <url-pattern>/hello</url-pattern>\n  </servlet-mapping>\n</web-app>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/webapp/index.html",
    "content": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=UTF-8\" />\n    <title>Hello World</title>\n  </head>\n\n  <body>\n    <h1>Hello World!</h1>\n\n    <table>\n      <tr>\n        <td colspan=\"2\" style=\"font-weight:bold;\">Available Servlets:</td>\n      </tr>\n      <tr>\n        <td><a href='/hello'>The HelloWorld servlet</a></td>\n      </tr>\n    </table>\n  </body>\n</html>\n"
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/webapp/META-INF/context.xml",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/webapp/Test.jsp",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/webapp/WEB-INF/classes/package/test.properties",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin/src/test/resources/gradle/webapp/WEB-INF/web.xml",
    "content": ""
  },
  {
    "path": "jib-gradle-plugin-extension-api/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n## 0.4.0\n\n### Changed\n\n- Upgraded jib-build-plan to 0.4.0. ([#2660](https://github.com/GoogleContainerTools/jib/pull/2660))\n"
  },
  {
    "path": "jib-gradle-plugin-extension-api/build.gradle",
    "content": "plugins {\n  id 'net.researchgate.release'\n  id 'maven-publish'\n  id 'eclipse'\n}\n\ndependencies {\n  api dependencyStrings.BUILD_PLAN\n  api dependencyStrings.EXTENSION_COMMON\n  api gradleApi()\n}\n\njar {\n  manifest {\n    attributes 'Implementation-Version': archiveVersion\n    attributes 'Automatic-Module-Name': 'com.google.cloud.tools.jib.gradle.extension'\n\n    // OSGi metadata\n    attributes 'Bundle-SymbolicName': 'com.google.cloud.tools.jib.gradle.extension'\n    attributes 'Bundle-Name': 'Extension API for Jib Gradle Plugin'\n    attributes 'Bundle-Vendor': 'Google LLC'\n    attributes 'Bundle-DocURL': 'https://github.com/GoogleContainerTools/jib'\n    attributes 'Bundle-License': 'https://www.apache.org/licenses/LICENSE-2.0'\n    attributes 'Export-Package': 'com.google.cloud.tools.jib.gradle.extension'\n  }\n}\n\n/* RELEASE */\nconfigureMavenRelease()\n\npublishing {\n  publications {\n    mavenJava(MavenPublication) {\n      pom {\n        name = 'Extension API for Jib Gradle Plugin'\n        description = 'Provides API to extend Jib Gradle Plugin containerization.'\n      }\n      from components.java\n    }\n  }\n}\n\n// Release plugin (git release commits and version updates)\nrelease {\n  tagTemplate = 'v$version-gradle-extension'\n  git {\n    requireBranch = /^gradle-extension-release-v\\d+.*$/  //regex\n  }\n}\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-gradle-plugin-extension-api/gradle.properties",
    "content": "version = 0.4.1-SNAPSHOT\n"
  },
  {
    "path": "jib-gradle-plugin-extension-api/kokoro/release_build.sh",
    "content": "#!/bin/bash\n\n# Fail on any error.\nset -o errexit\n# Display commands to stderr.\nset -o xtrace\n\ncd github/jib\n./gradlew :jib-gradle-plugin-extension-api:prepareRelease\n"
  },
  {
    "path": "jib-gradle-plugin-extension-api/src/main/java/com/google/cloud/tools/jib/gradle/extension/GradleData.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.extension;\n\nimport org.gradle.api.Project;\n\n/** Holds Gradle-specific data and properties. */\npublic interface GradleData {\n\n  Project getProject();\n}\n"
  },
  {
    "path": "jib-gradle-plugin-extension-api/src/main/java/com/google/cloud/tools/jib/gradle/extension/JibGradlePluginExtension.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.gradle.extension;\n\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtension;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport java.util.Map;\nimport java.util.Optional;\n\n/**\n * Jib Gradle plugin extension API.\n *\n * <p>If a class implementing the interface is visible on the classpath of the Jib Gradle plugin and\n * the plugin is configured to load the extension class, the Jib plugin extension framework calls\n * the interface method of the class.\n */\npublic interface JibGradlePluginExtension<T> extends JibPluginExtension {\n\n  /**\n   * The type of an custom configuration defined by this extension. The configuration object is\n   * mapped from {@code pluginExtensions.pluginExtension.configuration}. Often, it is sufficient to\n   * leverage {@code pluginExtensions.pluginExtension.properties} and the extension may not wish to\n   * define a custom configuration; in that case, use {@link Void} for &lt;T&gt; and have this\n   * method return {@code Optional#empty()}. (Don't return {@code Optional.of(Void.class)}.)\n   *\n   * @return type of an extension-specific custom configuration; {@code Optional.empty()} if no need\n   *     to define custom configuration\n   */\n  Optional<Class<T>> getExtraConfigType();\n\n  /**\n   * Extends the build plan prepared by the Jib Gradle plugin.\n   *\n   * @param buildPlan original build plan prepared by the Jib Gradle plugin\n   * @param properties custom properties configured for the plugin extension\n   * @param extraConfig extension-specific custom configuration mapped from {@code\n   *     jib.pluginExtensions.pluginExtension.configuration} of type type &lt;T&gt;. {@link\n   *     Optional#empty()} when {@link #getExtraConfigType()} returns {@link Optional#empty()} or\n   *     {@code pluginExtension.configuration} is not specified by the extension user.\n   * @param gradleData {@link GradleData} providing Gradle-specific data and properties\n   * @param logger logger for writing log messages\n   * @return updated build plan\n   * @throws JibPluginExtensionException if an error occurs while running the plugin extension\n   */\n  ContainerBuildPlan extendContainerBuildPlan(\n      ContainerBuildPlan buildPlan,\n      Map<String, String> properties,\n      Optional<T> extraConfig,\n      GradleData gradleData,\n      ExtensionLogger logger)\n      throws JibPluginExtensionException;\n}\n"
  },
  {
    "path": "jib-maven-plugin/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n## 3.5.1\n\n### Added\n- feat: support Java 25 main methods\n\n### Changed\n- deps: update `org.ow2.asm:asm` to version 9.9\n\n## 3.5.0\n\n### Added\n- feat: add default base image for Java 25 (#4436)\n\n### Changed\n- deps: update `org.ow2.asm:asm` to version 9.8 for java 25 support\n\n### Fixed\n\n## 3.4.6\n\n### Changed\n- Update retrieval of Maven properties to use the following order: user, project, system [#4344](https://github.com/GoogleContainerTools/jib/issues/4344)\n\n## 3.4.5\n\n### Fixed\n- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4267)\n\n\n## 3.4.4\n- fix: allow pushing images with different arch/os to docker daemon [#4265](https://github.com/GoogleContainerTools/jib/issues/4265)\n- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4265)\n\n## 3.4.3\n\n### Fixed\n- fix: When building to the local docker daemon with multiple platforms configured, Jib will now automatically select the image that matches the OS type and architecture of the local Docker environment. ([#4249](https://github.com/GoogleContainerTools/jib/pull/4249))\n\n## 3.4.2\n\n### Changed\n- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204))\n\n### Fixed\n- fix: set PAX headers to address build reproducibility issue ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204))\n- fix: (WAR Containerization) modify default entrypoint to `java -jar /usr/local/jetty/start.jar --module=ee10-deploy` for Jetty 12+ compatibility ([#4216](https://github.com/GoogleContainerTools/jib/pull/4216))\n\n## 3.4.1\n\n### Fixed\n- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171))\n\n## 3.4.0\n\n### Changed\n- deps: bump org.apache.maven:maven-compat from 3.9.1 to 3.9.2. ([#4017](https://github.com/GoogleContainerTools/jib/pull/4017/))\n- deps: bump com.github.luben:zstd-jni from 1.5.5-2 to 1.5.5-4. ([#4049](https://github.com/GoogleContainerTools/jib/pull/4049/))\n- deps: bump com.fasterxml.jackson:jackson-bom from 2.15.0 to 2.15.2. ([#4055](https://github.com/GoogleContainerTools/jib/pull/4055))\n- deps: bump com.google.guava:guava from 32.0.1-jre to 32.1.2-jre ([#4078](https://github.com/GoogleContainerTools/jib/pull/4078))\n- deps: bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9. ([#4098](https://github.com/GoogleContainerTools/jib/pull/4098))\n\n### Fixed\n- fix: fix WWW-Authenticate header parsing for Basic authentication ([#4035](https://github.com/GoogleContainerTools/jib/pull/4035/))\n\n## 3.3.2\n\n### Changed\n- Log an info instead of warning when entrypoint makes the image to ignore jvm parameters ([#3904](https://github.com/GoogleContainerTools/jib/pull/3904))\n\nThanks to our community contributors @rmannibucau!\n\n## 3.3.1\n\n### Changed\n- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745))\n\n## 3.3.0\n\n### Added\n\n- Included `imagePushed` field to image metadata json output file which provides information on whether an image was pushed by Jib. Note that the output file is `build/jib-image.json` by default or configurable with `jib.outputPaths.imageJson`. ([#3641](https://github.com/GoogleContainerTools/jib/pull/3641))\n- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)).\n- Support for OCI image index manifests ([#3715](https://github.com/GoogleContainerTools/jib/pull/3715)).\n- Support for base image layer compressed with zstd ([#3717](https://github.com/GoogleContainerTools/jib/pull/3717)).\n\n### Changed\n\n- Upgraded slf4j-simple and slf4j-api to 2.0.0 ([#3734](https://github.com/GoogleContainerTools/jib/pull/3734), [#3735](https://github.com/GoogleContainerTools/jib/pull/3735)).\n- Upgraded nullaway to 0.9.9. ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720))\n- Jib now only checks for file existence instead of running the executable passed into `dockerClient.executable` for the purpose of verifying if docker is installed correctly. Users are responsible for ensuring that the docker executable specified through this property is valid and has the correct permissions ([#3744](https://github.com/GoogleContainerTools/jib/pull/3744)).\n- Jib now throws an exception when the base image doesn't support target platforms during multi-platform build ([#3707](https://github.com/GoogleContainerTools/jib/pull/3707)).\n\nThanks to our community contributors @wwadge, @oliver-brm, @rquinio and @gsquared94!\n\n## 3.2.1\n\n### Added\n\n- Environment variables can now be used in configuring credential helpers. ([#2814](https://github.com/GoogleContainerTools/jib/issues/2814))\n  ```xml\n  <to>\n      <image>myimage</image>\n      <credHelper>\n          <helper>ecr-login</helper>\n          <environment>\n              <AWS_PROFILE>profile</AWS_PROFILE>\n          </environment>\n      </credHelper>\n  </to>\n  ```\n\n### Changed\n\n- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/pull/3612)).\n\n## 3.2.0\n\n### Added\n\n- [`<from><platforms>`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#from-object) parameter for multi-architecture image building can now be configured through Maven and system properties (for example, `-Djib.from.platforms=linux/amd64,linux/arm64` on the command-line). ([#2742](https://github.com/GoogleContainerTools/jib/pull/2742))\n- For retrieving credentials, Jib additionally looks for `$XDG_RUNTIME_DIR/containers/auth.json`, `$XDG_CONFIG_HOME/containers/auth.json`, and `$HOME/.config/containers/auth.json`. ([#3524](https://github.com/GoogleContainerTools/jib/issues/3524))\n\n\n### Changed\n\n- Changed the default base image of the Jib CLI `jar` command from the `adoptopenjdk` images to the [`eclipse-temurin`](https://hub.docker.com/_/eclipse-temurin) on Docker Hub. Note that Temurin (by Adoptium) is the new name of AdoptOpenJDK. ([#3483](https://github.com/GoogleContainerTools/jib/issues/3483))\n- Build will fail if `<extraDirectories><paths>` contain `from` directory that doesn't exist locally ([#3542](https://github.com/GoogleContainerTools/jib/issues/3542))\n\n### Fixed\n\n- Fixed incorrect parsing with comma escaping when providing Jib list or map property values on the command-line. ([#2224](https://github.com/GoogleContainerTools/jib/issues/2224))\n\n## 3.1.4\n\n### Changed\n\n- Downgraded Google HTTP libraries to 1.34.0 to resolve network issues. ([#3415](https://github.com/GoogleContainerTools/jib/pull/3415), [#3058](https://github.com/GoogleContainerTools/jib/issues/3058), [#3409](https://github.com/GoogleContainerTools/jib/issues/3409))\n- If `allowInsecureRegistries=true`, HTTP requests are retried on I/O errors only after insecure failover is finalized for each server. ([#3422](https://github.com/GoogleContainerTools/jib/issues/3422))\n\n## 3.1.3\n\n### Added\n\n- Increased robustness in registry communications by retrying HTTP requests (to the effect of retrying image pushes or pulls) on I/O exceptions with exponential backoffs. ([#3351](https://github.com/GoogleContainerTools/jib/pull/3351))\n- Now also supports `username` and `password` properties for the `auths` section in a Docker config (`~/.docker/config.json`). (Previously, only supported was a base64-encoded username and password string of the `auth` property.) ([#3365](https://github.com/GoogleContainerTools/jib/pull/3365))\n\n### Changed\n\n- Upgraded Google HTTP libraries to 1.39.2. ([#3387](https://github.com/GoogleContainerTools/jib/pull/3387))\n\n## 3.1.2\n\n### Fixed\n\n- Fixed the bug introduced in 3.1 that constructs a wrong Java runtime classpath when two dependencies have the same artifact ID and version but different group IDs. The bug occurs only when using Java 9+ or setting `<container><expandClasspathDependencies>`. ([#3331](https://github.com/GoogleContainerTools/jib/pull/3331))\n\n## 3.1.1\n\n### Fixed\n\n- Fixed the regression introduced in 3.1.0 where a build may fail due to an error from main class inference even if `<container><entrypoint>` is configured. ([#3295](https://github.com/GoogleContainerTools/jib/pull/3295))\n\n## 3.1.0\n\n### Added\n\n- For Google Artifact Registry (`*-docker.pkg.dev`), Jib now tries [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) last like it has been doing for `gcr.io`. ([#3241](https://github.com/GoogleContainerTools/jib/pull/3241))\n\n### Changed\n\n- Jib now creates an additional layer that contains two small text files: [`/app/jib-classpath-file` and `/app/jib-main-class-file`](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin/README.md#custom-container-entrypoint). They hold, respectively, the final Java runtime classpath and the main class computed by Jib that are suitable for app execution on JVM. For example, with Java 9+, setting the container entrypoint to `java --class-path @/app/jib-classpath-file @/app/jib-main-class-file` will work to start the app. (This is basically the default entrypoint set by Jib when the entrypoint is not explicitly configured by the user.) The files are always generated whether Java 8 or 9+, or whether `jib.container.entrypoint` is explicitly configured. The files can be helpful especially when setting a custom entrypoint for a shell script that needs to get the classpath and the main class computed by Jib, or for [AppCDS](https://github.com/GoogleContainerTools/jib/issues/2471). ([#3280](https://github.com/GoogleContainerTools/jib/pull/3280))\n- For Java 9+ apps, the default Java runtime classpath explicitly lists all the app dependencies, preserving the dependency loading order declared by Maven. This is done by changing the default entrypoint to use the new classpath JVM argument file (basically `java -cp @/app/jib-classpath-file`). As such, `<container><expandClasspathDependencies>` takes no effect for Java 9+. ([#3280](https://github.com/GoogleContainerTools/jib/pull/3280))\n- Timestamps of file entries in a tarball built with `jib:buildTar` are set to the epoch, making the tarball reproducible. ([#3158](https://github.com/GoogleContainerTools/jib/issues/3158))\n\n## 3.0.0\n\n### Added\n\n- New `<includes>` and `<excludes>` options for `<extraDirectories>`. This enables copying a subset of files from the source directory using glob patterns. ([#2564](https://github.com/GoogleContainerTools/jib/issues/2564))\n- [Jib extensions](https://github.com/GoogleContainerTools/jib-extensions) can be loaded via the [Maven dependency injection mechanism](https://maven.apache.org/maven-jsr330.html). This also enables injecting arbitrary dependencies (for example, Maven components) into an extension. ([#3036](https://github.com/GoogleContainerTools/jib/issues/3036))\n\n### Changed\n\n- [Switched the default base images](https://github.com/GoogleContainerTools/jib/blob/master/docs/default_base_image.md) from Distroless to [`adoptopenjdk:{8,11}-jre`](https://hub.docker.com/_/adoptopenjdk) and [`jetty`](https://hub.docker.com/_/jetty) (for WAR). ([#3124](https://github.com/GoogleContainerTools/jib/pull/3124))\n\n### Fixed\n\n- Fixed an issue where some log messages used color in the \"plain\" console output. ([#2764](https://github.com/GoogleContainerTools/jib/pull/2764))\n\n## 2.8.0\n\n### Added\n\n- Added support for [configuring registry mirrors](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#i-am-hitting-docker-hub-rate-limits-how-can-i-configure-registry-mirrors) for base images. This is useful when hitting [Docker Hub rate limits](https://www.docker.com/increase-rate-limits). Only public mirrors (such as `mirror.gcr.io`) are supported. ([#3011](https://github.com/GoogleContainerTools/jib/issues/3011))\n\n### Changed\n\n- Build will fail if Jib cannot create or read the [global Jib configuration file](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#global-jib-configuration). ([#2996](https://github.com/GoogleContainerTools/jib/pull/2996))\n\n## 2.7.1\n\n### Fixed\n\n- Updated jackson dependency version causing compatibility issues. ([#2931](https://github.com/GoogleContainerTools/jib/issues/2931))\n\n## 2.7.0\n\n### Changed\n\n- Added an option `<container><expandClasspathDependencies>` to preserve the order of loading dependencies as configured in a project. The option enumerates dependency JARs instead of using a wildcard (`/app/libs/*`) in the Java runtime classpath for an image entrypoint. ([#1871](https://github.com/GoogleContainerTools/jib/issues/1871), [#1907](https://github.com/GoogleContainerTools/jib/issues/1907), [#2228](https://github.com/GoogleContainerTools/jib/issues/2228), [#2733](https://github.com/GoogleContainerTools/jib/issues/2733))\n    - The option is also useful for AppCDS. ([#2471](https://github.com/GoogleContainerTools/jib/issues/2471))\n    - Turning on the option may result in a very long classpath string, and the OS may not support passing such a long string to JVM.\n\n### Fixed\n\n- Fixed `NullPointerException` when pulling an OCI base image whose manifest does not have `mediaType` information. ([#2819](https://github.com/GoogleContainerTools/jib/issues/2819))\n- Fixed build failure when using a Docker daemon base image (`docker://...`) that has duplicate layers. ([#2829](https://github.com/GoogleContainerTools/jib/issues/2829))\n\n## 2.6.0\n\n### Added\n\n### Changed\n\n- Previous locally cached base image manifests will be ignored, as the caching mechanism changed to enable multi-platform image building. ([#2730](https://github.com/GoogleContainerTools/jib/pull/2730), [#2711](https://github.com/GoogleContainerTools/jib/pull/2711))\n- Upgraded the ASM library to 9.0 to resolve an issue when auto-inferring main class in Java 15+. ([#2776](https://github.com/GoogleContainerTools/jib/pull/2776))\n- _Incubating feature_: can now configure multiple platforms (such as architectures) to build multiple images as a bundle and push as a manifest list (also known as a fat manifest). As an incubating feature, there are certain limitations. For example, OCI image indices are not supported, and building a manifest list is supported only for registry pushing (the `jib:build` goal). ([#2523](https://github.com/GoogleContainerTools/jib/issues/2523))\n    ```xml\n      <from>\n        <image>... image reference to a manifest list ...</image>\n        <platforms>\n          <platform>\n            <architecture>arm64</architecture>\n            <os>linux</os>\n          </platform>\n        </platforms>\n      </from>\n   ```\n\n### Fixed\n\n- Fixed authentication failure with Azure Container Registry when using [\"tokens\"](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-repository-scoped-permissions). ([#2784](https://github.com/GoogleContainerTools/jib/issues/2784))\n- Improved authentication flow for base image registry. ([#2134](https://github.com/GoogleContainerTools/jib/issues/2134))\n\n## 2.5.2\n\n### Fixed\n\n- Fixed the regression introduced in 2.5.1 that caused Jib to containerize a Spring Boot fat JAR instead of a normal thin JAR when `<containerizingMode>packaged` is set and the Spring Boot Maven plugin does not have a `<configuration>` block. ([#2693](https://github.com/GoogleContainerTools/jib/pull/2693))\n\n## 2.5.1\n\n### Fixed\n\n- Fixed `NullPointerException` when `<containerizingMode>packaged` is set the Spring Boot Maven plugin does not have a `<configuration>` block. ([#2687](https://github.com/GoogleContainerTools/jib/issues/2687))\n\n## 2.5.0\n\n### Added\n\n- Also tries `.exe` file extension for credential helpers on Windows. ([#2527](https://github.com/GoogleContainerTools/jib/issues/2527))\n- New system property `jib.skipExistingImages` (false by default) to skip pushing images (manifests) if the image already exists in the registry. ([#2360](https://github.com/GoogleContainerTools/jib/issues/2360))\n- _Incubating feature_: can now configure desired platform (architecture and OS) to select the matching manifest from a Docker manifest list for a base image. Currently supports building only one image. OCI image indices are not supported. ([#1567](https://github.com/GoogleContainerTools/jib/issues/1567))\n    ```xml\n      <from>\n        <image>... image reference to a manifest list ...</image>\n        <platforms>\n          <platform>\n            <architecture>arm64</architecture>\n            <os>linux</os>\n          </platform>\n        </platforms>\n      </from>\n    ```\n\n### Fixed\n\n- Fixed reporting a wrong credential helper name when the helper does not exist on Windows. ([#2527](https://github.com/GoogleContainerTools/jib/issues/2527))\n- Fixed `NullPointerException` when the `\"auths\":` section in `~/.docker/config.json` has an entry with no `\"auth\":` field. ([#2535](https://github.com/GoogleContainerTools/jib/issues/2535))\n- Fixed `NullPointerException` to return a helpful message when a server does not provide any message in certain error cases (400 Bad Request, 404 Not Found, and 405 Method Not Allowed). ([#2532](https://github.com/GoogleContainerTools/jib/issues/2532))\n- Now supports sending client certificate (for example, via the `javax.net.ssl.keyStore` and `javax.net.ssl.keyStorePassword` system properties) and thus enabling mutual TLS authentication. ([#2585](https://github.com/GoogleContainerTools/jib/issues/2585), [#2226](https://github.com/GoogleContainerTools/jib/issues/2226))\n- Fixed build failure with `<containerizingMode>packaged` in Spring Boot projects where Jib assumed a wrong JAR path when `<finalName>` or `<classifier>` is configured in Spring Boot. ([#2565](https://github.com/GoogleContainerTools/jib/issues/2565))\n- Fixed an issue where Jib cannot infer Kotlin main class that takes no arguments. ([#2666](https://github.com/GoogleContainerTools/jib/pull/2666))\n\n## 2.4.0\n\n### Added\n\n- Jib Extension Framework! The framework enables anyone to easily extend and tailor the Jib Maven plugin behavior to their liking. Check out the new [Jib Extensions](https://github.com/GoogleContainerTools/jib-extensions) GitHub repository to learn more. ([#2401](https://github.com/GoogleContainerTools/jib/issues/2401))\n- Project dependencies in a multi-module WAR project are now stored in a separate \"project dependencies\" layer (as currently done for a non-WAR project). ([#2450](https://github.com/GoogleContainerTools/jib/issues/2450))\n\n### Changed\n\n- Previous locally cached application layers (`<project root>/target/jib-cache`) will be ignored because of changes to the caching selectors. ([#2499](https://github.com/GoogleContainerTools/jib/pull/2499))\n\n### Fixed\n\n- Fixed authentication failure with Azure Container Registry when using an identity token defined in the `auths` section of Docker config (`~/.docker/config.json`). ([#2488](https://github.com/GoogleContainerTools/jib/pull/2488))\n\n## 2.3.0\n\n### Added\n\n- `<from>` and `<into>` fields to `<extraDirectories><paths><path>` for configuring the source and target of an extra directory. ([#1581](https://github.com/GoogleContainerTools/jib/issues/1581))\n\n### Fixed\n\n- Fixed the problem not inheriting `USER` container configuration from a base image. ([#2421](https://github.com/GoogleContainerTools/jib/pull/2421))\n- Fixed wrong capitalization of JSON properties in a loadable Docker manifest when building a tar image. ([#2430](https://github.com/GoogleContainerTools/jib/issues/2430))\n- Fixed an issue when using a base image whose image creation timestamp contains timezone offset. ([#2428](https://github.com/GoogleContainerTools/jib/issues/2428))\n- Fixed an issue inferring a wrong main class or using an invalid main class (for example, Spring Boot project containing multiple main classes). ([#2456](https://github.com/GoogleContainerTools/jib/issues/2456))\n\n## 2.2.0\n\n### Added\n\n- Glob pattern support for `<extraDirectories><permissions>`. ([#1200](https://github.com/GoogleContainerTools/jib/issues/1200))\n- Support for image references with both a tag and a digest. ([#1481](https://github.com/GoogleContainerTools/jib/issues/1481))\n- The `DOCKER_CONFIG` environment variable specifying the directory containing docker configs is now checked during credential retrieval. ([#1618](https://github.com/GoogleContainerTools/jib/issues/1618))\n- Also tries `.cmd` file extension for credential helpers on Windows. ([#2399](https://github.com/GoogleContainerTools/jib/issues/2399))\n\n### Changed\n\n- `<container><creationTime>` now accepts more timezone formats:`+HHmm`. This allows for easier configuration of creationTime by external systems. ([#2320](https://github.com/GoogleContainerTools/jib/issues/2320))\n\n## 2.1.0\n\n### Added\n\n- Additionally reads credentials from `~/.docker/.dockerconfigjson` and legacy Docker config (`~/.docker/.dockercfg`). Also searches for `$HOME/.docker/*` (in addition to current `System.get(\"user.home\")/.docker/*`). This may help retrieve credentials, for example, on Kubernetes. ([#2260](https://github.com/GoogleContainerTools/jib/issues/2260))\n- New skaffold configuration options that modify how jib's build config is presented to skaffold ([#2292](https://github.com/GoogleContainerTools/jib/pull/2292)):\n    - `<watch><buildIncludes>`: a list of build files to watch\n    - `<watch><includes>`: a list of project files to watch\n    - `<watch><excludes>`: a list of files to exclude from watching\n    - `<sync><excludes>`: a list of files to exclude from sync'ing\n\n### Fixed\n\n- Fixed a `skaffold init` issue with projects containing submodules specifying different parent poms. ([#2262](https://github.com/GoogleContainerTools/jib/issues/2262))\n- Fixed authentication failure with error `server did not return 'WWW-Authenticate: Bearer' header` in certain cases (for example, on OpenShift). ([#2258](https://github.com/GoogleContainerTools/jib/issues/2258))\n- Fixed an issue where using local Docker images (by `docker://...`) on Windows caused an error. ([#2270](https://github.com/GoogleContainerTools/jib/issues/2270))\n\n## 2.0.0\n\n### Added\n\n- Added json output file for image metadata after a build is complete. Writes to `target/jib-image.json` by default, configurable with `<outputPaths><imageJson>`. ([#2227](https://github.com/GoogleContainerTools/jib/pull/2227))\n- Added automatic update checks. Jib will now display a message if there is a new version of Jib available. See the [privacy page](../docs/privacy.md) for more details. ([#2193](https://github.com/GoogleContainerTools/jib/issues/2193))\n\n### Changed\n\n- Removed deprecated `<extraDirectory>` configuration in favor of `<extraDirectories>`. ([#1691](https://github.com/GoogleContainerTools/jib/issues/1691))\n- Removed deprecated `<container><useCurrentTimestamp>` configuration in favor of `<container><creationTime>` with `USE_CURRENT_TIMESTAMP`. ([#1897](https://github.com/GoogleContainerTools/jib/issues/1897))\n- HTTP redirection URLs are no longer sanitized in order to work around an issue with certain registries that do not conform to HTTP standards. This resolves an issue with using Red Hat OpenShift and Quay registries. ([#2106](https://github.com/GoogleContainerTools/jib/issues/2106), [#1986](https://github.com/GoogleContainerTools/jib/issues/1986#issuecomment-547610104))\n- The default base image cache location has been changed on MacOS and Windows. ([#2216](https://github.com/GoogleContainerTools/jib/issues/2216))\n    - MacOS (`$XDG_CACHE_HOME` defined): from `$XDG_CACHE_HOME/google-cloud-tools-java/jib/` to `$XDG_CACHE_HOME/Google/Jib/`\n    - MacOS (`$XDG_CACHE_HOME` not defined): from `$HOME/Library/Application Support/google-cloud-tools-java/jib/` to `$HOME/Library/Caches/Google/Jib/`\n    - Windows (`$XDG_CACHE_HOME` defined): from `$XDG_CACHE_HOME\\google-cloud-tools-java\\jib\\` to `$XDG_CACHE_HOME\\Google\\Jib\\Cache\\`\n    - Windows (`$XDG_CACHE_HOME` not defined): from `%LOCALAPPDATA%\\google-cloud-tools-java\\jib\\` to `%LOCALAPPDATA%\\Google\\Jib\\Cache\\`\n    - Initial builds will be slower until the cache is repopulated, unless you manually move the cache from the old location to the new location\n- When giving registry credentials in `settings.xml`, specifying port in `<server><id>` is no longer required. ([#2135](https://github.com/GoogleContainerTools/jib/issues/2135))\n\n### Fixed\n\n- Fixed `<extraDirectories><permissions>` being ignored if `<paths>` are not explicitly defined. ([#2106](https://github.com/GoogleContainerTools/jib/issues/2160))\n- Now `<containerizingMode>packaged` works as intended with Spring Boot projects that generate a fat JAR. ([#2170](https://github.com/GoogleContainerTools/jib/issues/2170))\n- Now `<containerizingMode>packaged` correctly identifies the packaged JAR generated at a non-default location when configured with the Maven Jar Plugin's `<classifier>` and `<outputDirectory>`. ([#2170](https://github.com/GoogleContainerTools/jib/issues/2170))\n- `jib:buildTar` with `<container><format>OCI` now builds a correctly formatted OCI archive. ([#2124](https://github.com/GoogleContainerTools/jib/issues/2124))\n- Fixed an issue where configuring the `<warName>` property of the Maven WAR plugin fails the build. ([#2206](https://github.com/GoogleContainerTools/jib/issues/2206))\n- Now automatically refreshes Docker registry authentication tokens when expired, fixing the issue that long-running builds may fail with \"401 unauthorized.\" ([#691](https://github.com/GoogleContainerTools/jib/issues/691))\n\n## 1.8.0\n\n### Changed\n\n- Optimized building to a registry with local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))\n\n### Fixed\n\n- Fixed reporting wrong module name when `skaffold init` is run on multi-module projects. ([#2088](https://github.com/GoogleContainerTools/jib/issues/2088))\n- `<allowInsecureRegistries>` and the `sendCredentialsOverHttp` system property are now effective for authentication service server connections. ([#2074](https://github.com/GoogleContainerTools/jib/pull/2074))\n- Fixed inefficient communications when interacting with insecure registries and servers (when `<allowInsecureRegistries>` is set). ([#946](https://github.com/GoogleContainerTools/jib/issues/946))\n\n## 1.7.0\n\n### Added\n\n- `<outputPaths>` object for configuration output file locations ([#1561](https://github.com/GoogleContainerTools/jib/issues/1561))\n  - `<outputPaths><tar>` configures output path of `jib:buildTar` (`target/jib-image.tar` by default)\n  - `<outputPaths><digest>` configures the output path of the image digest (`target/jib-image.digest` by default)\n  - `<outputPaths><imageId>` configures output path of the image id  (`target/jib-image.id` by default)\n- Main class inference support for Java 13/14. ([#2015](https://github.com/GoogleContainerTools/jib/issues/2015))\n\n### Changed\n\n- Local base image layers are now processed in parallel, speeding up builds using large local base images. ([#1913](https://github.com/GoogleContainerTools/jib/issues/1913))\n- The base image manifest is no longer pulled from the registry if a digest is provided and the manifest is already cached. ([#1881](https://github.com/GoogleContainerTools/jib/issues/1881))\n- Docker daemon base images are now cached more effectively, speeding up builds using `docker://` base images. ([#1912](https://github.com/GoogleContainerTools/jib/issues/1912))\n\n### Fixed\n\n- Fixed temporary directory cleanup during builds using local base images. ([#2016](https://github.com/GoogleContainerTools/jib/issues/2016))\n- Fixed additional tags being ignored when building to a tarball. ([#2043](https://github.com/GoogleContainerTools/jib/issues/2043))\n- Fixed `tar://` base image failing if tar does not contain explicit directory entries. ([#2067](https://github.com/GoogleContainerTools/jib/issues/2067))\n- Fixed an issue for WAR projects where Jib used an intermediate exploded WAR directory instead of exploding the final WAR file. ([#1091](https://github.com/GoogleContainerTools/jib/issues/1091))\n\n## 1.6.1\n\n### Fixed\n\n- Fixed an issue with using custom base images in Java 12+ projects. ([#1995](https://github.com/GoogleContainerTools/jib/issues/1995))\n\n## 1.6.0\n\n### Added\n\n- Support for local base images by prefixing `<from><image>` with `docker://` to build from a docker daemon image, or `tar://` to build from a tarball image. ([#1468](https://github.com/GoogleContainerTools/jib/issues/1468), [#1905](https://github.com/GoogleContainerTools/jib/issues/1905))\n\n### Changed\n\n- To disable parallel execution, the property `jib.serialize` should be used instead of `jibSerialize`. ([#1968](https://github.com/GoogleContainerTools/jib/issues/1968))\n- For retrieving credentials from Docker config (`~/.docker/config.json`), `credHelpers` now takes precedence over `credsStore`, followed by `auths`. ([#1958](https://github.com/GoogleContainerTools/jib/pull/1958))\n- The legacy `credsStore` no longer requires defining empty registry entries in `auths` to be used. This now means that if `credsStore` is defined, `auths` will be completely ignored. ([#1958](https://github.com/GoogleContainerTools/jib/pull/1958))\n- `<dockerClient>` is now configurable on all goals, not just `jib:dockerBuild`. ([#1932](https://github.com/GoogleContainerTools/jib/issues/1932))\n\n### Fixed\n\n- Fixed the regression of slow network operations introduced at 1.5.0. ([#1980](https://github.com/GoogleContainerTools/jib/pull/1980))\n- Fixed an issue where connection timeout sometimes fell back to attempting plain HTTP (non-HTTPS) requests when `<allowInsecureRegistries>` is set. ([#1949](https://github.com/GoogleContainerTools/jib/pull/1949))\n\n## 1.5.1\n\n### Fixed\n\n- Fixed an issue interacting with certain registries due to changes to URL handling in the underlying Apache HttpClient library. ([#1924](https://github.com/GoogleContainerTools/jib/issues/1924))\n\n## 1.5.0\n\n### Added\n\n- Can now set file timestamps (last modified time) in the image with `<container><filesModificationTime>`. The value should either be `EPOCH_PLUS_SECOND` to set the timestamps to Epoch + 1 second (default behavior), or an ISO 8601 date time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`. ([#1818](https://github.com/GoogleContainerTools/jib/pull/1818))\n- Can now set container creation timestamp with `<container><creationTime>`. The value should be `EPOCH`, `USE_CURRENT_TIMESTAMP`, or an ISO 8601 date time. ([#1609](https://github.com/GoogleContainerTools/jib/issues/1609))\n- For Google Container Registry (gcr.io), Jib now tries [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials) (ADC) last when no credentials can be retrieved. ADC are available on many Google Cloud Platform (GCP) environments (such as Google Cloud Build, Google Compute Engine, Google Kubernetes Engine, and Google App Engine). Application Default Credentials can also be configured with `gcloud auth application-default login` locally or through the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. ([#1902](https://github.com/GoogleContainerTools/jib/pull/1902))\n\n### Changed\n\n- When building to a registry, Jib now skips downloading and caching base image layers that already exist in the target registry. This feature will be particularly useful in CI/CD environments. However, if you want to force caching base image layers locally, set the system property `-Djib.alwaysCacheBaseImage=true`. ([#1840](https://github.com/GoogleContainerTools/jib/pull/1840))\n- `<container><useCurrentTimestamp>` has been deprecated in favor of `<container><creationTime>` with `USE_CURRENT_TIMESTAMP`. ([#1609](https://github.com/GoogleContainerTools/jib/issues/1609))\n\n## 1.4.0\n\n### Added\n\n- Can now containerize a JAR artifact instead of putting individual `.class` and resource files with `<containerizingMode>packaged`. ([#1746](https://github.com/GoogleContainerTools/jib/pull/1746/files))\n- Can now use `<from><image>scratch` to use the scratch (empty) base image for builds. ([#1794](https://github.com/GoogleContainerTools/jib/pull/1794/files))\n\n### Changed\n\n- Dependencies are now split into three layers: dependencies, snapshots dependencies, project dependencies. ([#1724](https://github.com/GoogleContainerTools/jib/pull/1724))\n\n### Fixed\n\n- Re-enabled cross-repository blob mounts. ([#1793](https://github.com/GoogleContainerTools/jib/pull/1793))\n- Manifest lists referenced directly by sha256 are automatically parsed and the first `linux/amd64` manifest is used. ([#1811](https://github.com/GoogleContainerTools/jib/issues/1811))\n\n## 1.3.0\n\n### Changed\n\n- Docker credentials (`~/.docker/config.json`) are now given priority over registry-based inferred credential helpers. ([#1704](https://github.com/GoogleContainerTools/jib/pulls/1704))\n\n### Fixed\n\n- Fixed an issue where decyrpting Maven settings `settings.xml` wholesale caused the build to fail. We now decrypt only the parts that are required. ([#1709](https://github.com/GoogleContainerTools/jib/issues/1709))\n\n## 1.2.0\n\n### Added\n\n- Container configurations in the base image are now propagated when registry uses the old V2 image manifest, schema version 1 (such as Quay). ([#1641](https://github.com/GoogleContainerTools/jib/issues/1641))\n- Can now prepend paths in the container to the computed classpath with `<container><extraClasspath>`. ([#1642](https://github.com/GoogleContainerTools/jib/pull/1642))\n- Can now build in offline mode using `--offline`. ([#718](https://github.com/GoogleContainerTools/jib/issues/718))\n- Now supports multiple extra directories with `<extraDirectories>{<paths><path>|<permissions>}`. ([#1020](https://github.com/GoogleContainerTools/jib/issues/1020))\n\n### Changed\n\n- `<extraDirectory>(<path>|<permissions>)` are deprecated in favor of the new `<extraDirectories>{<paths><path>|<permissions>}` configurations. ([#1626](https://github.com/GoogleContainerTools/jib/pull/1626))\n\n### Fixed\n\n- Labels in the base image are now propagated. ([#1643](https://github.com/GoogleContainerTools/jib/issues/1643))\n- Fixed an issue with using OCI base images. ([#1683](https://github.com/GoogleContainerTools/jib/issues/1683))\n\n## 1.1.2\n\n### Fixed\n\n- Fixed an issue where automatically generated parent directories in a layer did not get their timestamp configured correctly to epoch + 1s. ([#1648](https://github.com/GoogleContainerTools/jib/issues/1648))\n\n## 1.1.1\n\n### Fixed\n\n- Fixed an issue where the plugin creates wrong images by adding base image layers in reverse order when registry uses the old V2 image manifest, schema version 1 (such as Quay). ([#1627](https://github.com/GoogleContainerTools/jib/issues/1627))\n\n## 1.1.0\n\n### Added\n\n- Can now decrypt proxy configurations in `settings.xml`. ([#1369](https://github.com/GoogleContainerTools/jib/issues/1369))\n\n### Changed\n\n- `os` and `architecture` are taken from base image. ([#1564](https://github.com/GoogleContainerTools/jib/pull/1564))\n\n### Fixed\n\n- Fixed an issue where pushing to Docker Hub fails when the host part of an image reference is `docker.io`. ([#1549](https://github.com/GoogleContainerTools/jib/issues/1549))\n\n## 1.0.2\n\n### Added\n\n- Java 9+ WAR projects are now supported and run on the distroless Jetty Java 11 image (https://github.com/GoogleContainerTools/distroless) by default. Java 8 projects remain on the distroless Jetty Java 8 image. ([#1510](https://github.com/GoogleContainerTools/jib/issues/1510))\n- Now supports authentication against Azure Container Registry using `docker-credential-acr-*` credential helpers. ([#1490](https://github.com/GoogleContainerTools/jib/issues/1490))\n- Batch mode now disables build progress bar. ([#1513](https://github.com/GoogleContainerTools/jib/issues/1513))\n\n### Fixed\n\n- Fixed an issue where setting `<allowInsecureRegistries>` may fail to try HTTP. ([#1517](https://github.com/GoogleContainerTools/jib/issues/1517))\n- Crash on talking to servers that do not set the `Content-Length` HTTP header or send an incorrect value. ([#1512](https://github.com/GoogleContainerTools/jib/issues/1512))\n\n## 1.0.1\n\n### Added\n\n- Java 9+ projects are now supported and run on the distroless Java 11 image (https://github.com/GoogleContainerTools/distroless) by default. Java 8 projects remain on the distroless Java 8 image. ([#1279](https://github.com/GoogleContainerTools/jib/issues/1279))\n\n### Fixed\n\n- Failure to infer main class when main method is defined using varargs (i.e. `public static void main(String... args)`). ([#1456](https://github.com/GoogleContainerTools/jib/issues/1456))\n\n## 1.0.0\n\n### Changed\n\n- Shortened progress bar display - make sure console window is at least 50 characters wide or progress bar display can be messy. ([#1361](https://github.com/GoogleContainerTools/jib/issues/1361))\n\n## 1.0.0-rc2\n\n### Added\n\n- Setting proxy credentials (via system properties `http(s).proxyUser` and `http(s).proxyPassword`) is now supported.\n- Maven proxy settings are now supported.\n- Now checks for system properties in pom as well as commandline. ([#1201](https://github.com/GoogleContainerTools/jib/issues/1201))\n- `<dockerClient><executable>` and `<dockerClient><environment>` to set Docker client binary path (defaulting to `docker`) and additional environment variables to apply when running the binary. ([#468](https://github.com/GoogleContainerTools/jib/issues/468))\n\n### Changed\n\n- Java 9+ projects using the default distroless Java 8 base image will now fail to build. ([#1143](https://github.com/GoogleContainerTools/jib/issues/1143))\n\n## 1.0.0-rc1\n\n### Added\n\n- `jib.baseImageCache` and `jib.applicationCache` system properties for setting cache directories. ([#1238](https://github.com/GoogleContainerTools/jib/issues/1238))\n- Build progress shown via a progress bar - set `-Djib.console=plain` to show progress as log messages. ([#1297](https://github.com/GoogleContainerTools/jib/issues/1297))\n\n### Changed\n\n- `gwt-app` packaging type now builds a WAR container.\n- When building to Docker and no `<to><image>` is defined, artifact ID is used as an image reference instead of project name.\n- Removed `<useOnlyProjectCache>` parameter in favor of the `jib.useOnlyProjectCache` system property. ([#1308](https://github.com/GoogleContainerTools/jib/issues/1308))\n\n### Fixed\n\n- Builds failing due to dependency JARs with the same name. ([#810](https://github.com/GoogleContainerTools/jib/issues/810))\n\n## 0.10.1\n\n### Added\n\n- Image ID is now written to `target/jib-image.id`. ([#1204](https://github.com/GoogleContainerTools/jib/issues/1204))\n- `<container><entrypoint>INHERIT</entrypoint></container>` allows inheriting `ENTRYPOINT` and `CMD` from the base image. While inheriting `ENTRYPOINT`, you can also override `CMD` using `<container><args>`.\n- `<container><workingDirectory>` configuration parameter to set the working directory. ([#1225](https://github.com/GoogleContainerTools/jib/issues/1225))\n- Adds support for configuring volumes. ([#1121](https://github.com/GoogleContainerTools/jib/issues/1121))\n- Exposed ports are now propagated from the base image. ([#595](https://github.com/GoogleContainerTools/jib/issues/595))\n- Docker health check is now propagated from the base image. ([#595](https://github.com/GoogleContainerTools/jib/issues/595))\n\n### Changed\n\n- Removed `jib:exportDockerContext` goal. ([#1219](https://github.com/GoogleContainerTools/jib/issues/1219))\n\n### Fixed\n\n- NullPointerException thrown with incomplete `auth` configuration. ([#1177](https://github.com/GoogleContainerTools/jib/issues/1177))\n\n## 0.10.0\n\n### Added\n\n- Properties for each configuration parameter, allowing any parameter to be set via commandline. ([#728](https://github.com/GoogleContainerTools/jib/issues/728))\n- `<to><credHelper>` and `<from><credHelper>` can be used to specify a credential helper suffix or a full path to a credential helper executable. ([#925](https://github.com/GoogleContainerTools/jib/issues/925))\n- `<container><user>` configuration parameter to configure the user and group to run the container as. ([#1029](https://github.com/GoogleContainerTools/jib/issues/1029))\n- Preliminary support for building images for WAR projects. ([#431](https://github.com/GoogleContainerTools/jib/issues/431))\n- `<extraDirectory>` object with a `<path>` and `<permissions>` field. ([#794](https://github.com/GoogleContainerTools/jib/issues/794))\n  - `<extraDirectory><path>` configures the extra layer directory (still also configurable via `<extraDirectory>...</extraDirectory>`)\n  - `<extraDirectory><permissions>` is a list of `<permission>` objects, each with a `<file>` and `<mode>` field, used to map a file on the container to the file's permission bits (represented as an octal string)\n- Image digest is now written to `target/jib-image.digest`. ([#1155](https://github.com/GoogleContainerTools/jib/pull/1155))\n- Adds the layer type to the layer history as comments. ([#1198](https://github.com/GoogleContainerTools/jib/issues/1198))\n\n### Changed\n\n- Removed deprecated `<jvmFlags>`, `<mainClass>`, `<args>`, and `<format>` in favor of the equivalents under `<container>`. ([#461](https://github.com/GoogleContainerTools/jib/issues/461))\n- `jib:exportDockerContext` generates different directory layout and `Dockerfile` to enable WAR support. ([#1007](https://github.com/GoogleContainerTools/jib/pull/1007))\n- File timestamps in the built image are set to 1 second since the epoch (hence 1970-01-01T00:00:01Z) to resolve compatibility with applications on Java 6 or below where the epoch means nonexistent or I/O errors; previously they were set to the epoch. ([#1079](https://github.com/GoogleContainerTools/jib/issues/1079))\n\n## 0.9.13\n\n### Fixed\n\n- Adds environment variable configuration to Docker context generator. ([#890 (comment)](https://github.com/GoogleContainerTools/jib/issues/890#issuecomment-430227555))\n\n## 0.9.11\n\n### Added\n\n- `<skip>` configuration parameter to skip Jib execution in multi-module projects (also settable via `jib.skip` property). ([#865](https://github.com/GoogleContainerTools/jib/issues/865))\n- `<container><environment>` configuration parameter to configure environment variables. ([#890](https://github.com/GoogleContainerTools/jib/issues/890))\n- `container.appRoot` configuration parameter to configure app root in the image. ([#984](https://github.com/GoogleContainerTools/jib/pull/984))\n- `<to><tags>` (list) defines additional tags to push to. ([#1026](https://github.com/GoogleContainerTools/jib/pull/1026))\n\n### Fixed\n\n- Keep duplicate layers to match container history. ([#1017](https://github.com/GoogleContainerTools/jib/pull/1017))\n\n## 0.9.10\n\n### Added\n\n- `<container><labels>` configuration parameter for configuring labels. ([#751](https://github.com/GoogleContainerTools/jib/issues/751))\n- `<container><entrypoint>` configuration parameter to set the entrypoint. ([#579](https://github.com/GoogleContainerTools/jib/issues/579))\n- `history` to layer metadata. ([#875](https://github.com/GoogleContainerTools/jib/issues/875))\n- Propagates working directory from the base image. ([#902](https://github.com/GoogleContainerTools/jib/pull/902))\n\n### Fixed\n\n- Corrects permissions for directories in the container filesystem. ([#772](https://github.com/GoogleContainerTools/jib/pull/772))\n\n## 0.9.9\n\n### Added\n\n- Passthrough labels from base image. ([#750](https://github.com/GoogleContainerTools/jib/pull/750/files))\n\n### Changed\n\n- Reordered classpath in entrypoint to allow dependency patching. ([#777](https://github.com/GoogleContainerTools/jib/issues/777))\n\n### Fixed\n\n## 0.9.8\n\n### Added\n\n- `<to><auth>` and `<from><auth>` parameters with `<username>` and `<password>` fields for simple authentication, similar to the Gradle plugin. ([#693](https://github.com/GoogleContainerTools/jib/issues/693))\n- Can set credentials via commandline using `jib.to.auth.username`, `jib.to.auth.password`, `jib.from.auth.username`, and `jib.from.auth.password` system properties. ([#693](https://github.com/GoogleContainerTools/jib/issues/693))\n- Docker context generation now includes snapshot dependencies and extra files. ([#516](https://github.com/GoogleContainerTools/jib/pull/516/files))\n- Disable parallel operation by setting the `jibSerialize` system property to `true`. ([#682](https://github.com/GoogleContainerTools/jib/pull/682))\n\n### Changed\n\n- Propagates environment variables from the base image. ([#716](https://github.com/GoogleContainerTools/jib/pull/716))\n- Skips execution if packaging is `pom`. ([#735](https://github.com/GoogleContainerTools/jib/pull/735))\n- `allowInsecureRegistries` allows connecting to insecure HTTPS registries (for example, registries using self-signed certificates). ([#733](https://github.com/GoogleContainerTools/jib/pull/733))\n\n### Fixed\n\n- Slow image reference parsing. ([#680](https://github.com/GoogleContainerTools/jib/pull/680))\n- Building empty layers. ([#516](https://github.com/GoogleContainerTools/jib/pull/516/files))\n- Duplicate layer entries causing unbounded cache growth. ([#721](https://github.com/GoogleContainerTools/jib/issues/721))\n- Incorrect authentication error message when target and base registry are the same. ([#758](https://github.com/GoogleContainerTools/jib/issues/758))\n\n## 0.9.7\n\n### Added\n\n- Snapshot dependencies are added as their own layer. ([#584](https://github.com/GoogleContainerTools/jib/pull/584))\n- `jib:buildTar` goal to build an image tarball at `target/jib-image.tar`, which can be loaded into docker using `docker load`. ([#514](https://github.com/GoogleContainerTools/jib/issues/514))\n- `<container><useCurrentTimestamp>` parameter to set the image creation time to the build time. ([#413](https://github.com/GoogleContainerTools/jib/issues/413))\n- Authentication over HTTP using the `sendCredentialsOverHttp` system property. ([#599](https://github.com/GoogleContainerTools/jib/issues/599))\n- HTTP connection and read timeouts for registry interactions configurable with the `jib.httpTimeout` system property. ([#656](https://github.com/GoogleContainerTools/jib/pull/656))\n\n### Changed\n\n- Docker context export parameter `-Djib.dockerDir` to `-DjibTargetDir`. ([#662](https://github.com/GoogleContainerTools/jib/issues/662))\n\n### Fixed\n\n- Using multi-byte characters in container configuration. ([#626](https://github.com/GoogleContainerTools/jib/issues/626))\n- For Docker Hub, also tries registry aliases when getting a credential from the Docker config. ([#605](https://github.com/GoogleContainerTools/jib/pull/605))\n- Decrypting credentials from Maven settings. ([#592](https://github.com/GoogleContainerTools/jib/issues/592))\n\n## 0.9.6\n\n### Fixed\n\n- Using a private registry that does token authentication with `allowInsecureRegistries` set to `true`. ([#572](https://github.com/GoogleContainerTools/jib/pull/572))\n\n## 0.9.5\n\n### Added\n\n- Incubating feature to build `src/main/jib` as extra layer in image. ([#565](https://github.com/GoogleContainerTools/jib/pull/565))\n\n## 0.9.4\n\n### Fixed\n\n- Fixed handling case-insensitive `Basic` authentication method. ([#546](https://github.com/GoogleContainerTools/jib/pull/546))\n- Fixed regression that broke pulling base images from registries that required token authentication. ([#549](https://github.com/GoogleContainerTools/jib/pull/549))\n\n## 0.9.3\n\n### Fixed\n\n- Using Docker config for finding registry credentials (was not ignoring extra fields and handling `https` protocol). ([#524](https://github.com/GoogleContainerTools/jib/pull/524))\n\n## 0.9.2\n\n### Changed\n\n- Minor improvements and issue fixes.\n\n## 0.9.1\n\n### Added\n\n- `<container><ports>` parameter to define container's exposed ports (similar to Dockerfile `EXPOSE`). ([#383](https://github.com/GoogleContainerTools/jib/issues/383))\n- Can set `allowInsecureRegistries` parameter to `true` to use registries that only support HTTP. ([#388](https://github.com/GoogleContainerTools/jib/issues/388))\n\n### Changed\n\n- Fetches credentials from inferred credential helper before Docker config. ([#401](https://github.com/GoogleContainerTools/jib/issues/401))\n- Container creation date set to timestamp 0. ([#341](https://github.com/GoogleContainerTools/jib/issues/341))\n- Does not authenticate base image pull unless necessary - reduces build time by about 500ms. ([#414](https://github.com/GoogleContainerTools/jib/pull/414))\n- `jvmFlags`, `mainClass`, `args`, and `format` are now grouped under `container` configuration object. ([#384](https://github.com/GoogleContainerTools/jib/issues/384))\n\n### Fixed\n\n- Using Azure Container Registry now works - define credentials in Maven settings. ([#415](https://github.com/GoogleContainerTools/jib/issues/415))\n- Supports `access_token` as alias to `token` in registry authentication. ([#420](https://github.com/GoogleContainerTools/jib/pull/420))\n\n## 0.9.0\n\n### Added\n\n- Better feedback for build failures. ([#197](https://github.com/google/jib/pull/197))\n- Warns if specified `mainClass` is not a valid Java class. ([#206](https://github.com/google/jib/issues/206))\n- Warns if build may not be reproducible. ([#245](https://github.com/GoogleContainerTools/jib/pull/245))\n- `jib:dockerBuild` maven goal to build straight to Docker daemon. ([#266](https://github.com/GoogleContainerTools/jib/pull/266))\n- `mainClass` is inferred by searching through class files if configuration is missing. ([#278](https://github.com/GoogleContainerTools/jib/pull/278))\n- Can now specify target image with `-Dimage`. ([#328](https://github.com/GoogleContainerTools/jib/issues/328))\n- `args` parameter to define default main args. ([#346](https://github.com/GoogleContainerTools/jib/issues/346))\n\n### Changed\n\n- Removed `enableReproducibleBuilds` parameter - application layers will always be reproducible. ([#245](https://github.com/GoogleContainerTools/jib/pull/245))\n- Changed configuration schema to be more like configuration for `jib-gradle-plugin` - NOT compatible with prior versions of `jib-maven-plugin`. ([#212](https://github.com/GoogleContainerTools/jib/issues/212))\n- `jib:dockercontext` has been changed to `jib:exportDockerContext`. ([#350](https://github.com/GoogleContainerTools/jib/issues/350))\n\n### Fixed\n\n- Directories in resources are added to classes layer. ([#318](https://github.com/GoogleContainerTools/jib/issues/318))\n\n## 0.1.7\n\n### Fixed\n\n- Using base images that lack entrypoints. ([#284](https://github.com/GoogleContainerTools/jib/pull/284))\n\n## 0.1.6\n\n### Changed\n\n- Base image layers are now cached on a user-level rather than a project level - disable with `useOnlyProjectCache` configuration. ([#29](https://github.com/google/jib/issues/29))\n\n### Fixed\n\n- `jib:dockercontext` not building a `Dockerfile`. ([#171](https://github.com/google/jib/pull/171))\n- Failure to parse Docker config with `HttpHeaders` field. ([#175](https://github.com/google/jib/pull/175))\n\n## 0.1.5\n\n### Added\n\n- Export a Docker context (including a Dockerfile) with `jib:dockercontext`. ([#49](https://github.com/google/jib/issues/49))\n\n## 0.1.4\n\n### Fixed\n\n- Null tag validation generating NullPointerException. ([#125](https://github.com/google/jib/issues/125))\n- Build failure on project with no dependencies. ([#126](https://github.com/google/jib/issues/126))\n\n## 0.1.3\n\n### Added\n\n- Build and push OCI container image. ([#96](https://github.com/google/jib/issues/96))\n\n## 0.1.2\n\n### Added\n\n- Use credentials from Docker config if none can be found otherwise. ([#101](https://github.com/google/jib/issues/101))\n- Reproducible image building. ([#7](https://github.com/google/jib/issues/7))\n\n## 0.1.1\n\n### Added\n\n- Simple example `helloworld` project under `examples/`. ([#62](https://github.com/google/jib/pull/62))\n- Better error messages when pushing an image manifest. ([#63](https://github.com/google/jib/pull/63))\n- Validates target image configuration. ([#63](https://github.com/google/jib/pull/63))\n- Configure multiple credential helpers with `credHelpers`. ([#68](https://github.com/google/jib/pull/68))\n- Configure registry credentials with Maven settings. ([#81](https://github.com/google/jib/pull/81))\n\n### Changed\n\n- Removed configuration `credentialHelperName`. ([#68](https://github.com/google/jib/pull/68))\n\n### Fixed\n\n- Build failure on Windows. ([#74](https://github.com/google/jib/issues/74))\n- Infers common credential helper names (for GCR and ECR). ([#64](https://github.com/google/jib/pull/64))\n- Cannot use private base image. ([#68](https://github.com/google/jib/pull/68))\n- Building applications with no resources. ([#73](https://github.com/google/jib/pull/73))\n- Pushing to registries like Docker Hub and ACR. ([#75](https://github.com/google/jib/issues/75))\n- Cannot build with files having long file names (> 100 chars). ([#91](https://github.com/google/jib/issues/91))\n"
  },
  {
    "path": "jib-maven-plugin/README.md",
    "content": "![stable](https://img.shields.io/badge/stability-stable-brightgreen.svg)\n[![Maven Central](https://img.shields.io/maven-central/v/com.google.cloud.tools/jib-maven-plugin)](https://maven-badges.herokuapp.com/maven-central/com.google.cloud.tools/jib-maven-plugin)\n[![Gitter version](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/google/jib)\n\n# Jib - Containerize your Maven project\n\nJib is a [Maven](https://maven.apache.org/) plugin for building Docker and [OCI](https://github.com/opencontainers/image-spec) images for your Java applications.\n\nFor the Gradle plugin, see the [jib-gradle-plugin project](../jib-gradle-plugin).\n\nFor information about the project, see the [Jib project README](../README.md).\n\n| ☑️  Jib User Survey |\n| :----- |\n| What do you like best about Jib? What needs to be improved? Please tell us by taking a [one-minute survey](https://forms.gle/YRFeamGj51xmgnx28). Your responses will help us understand Jib usage and allow us to serve our customers (you!) better. |\n\n## Table of Contents\n\n* [Upcoming Features](#upcoming-features)\n* [Quickstart](#quickstart)\n  * [Setup](#setup)\n  * [Configuration](#configuration)\n  * [Build your image](#build-your-image)\n    * [Build to Docker Daemon](#build-to-docker-daemon)\n    * [Build an image tarball](#build-an-image-tarball)\n  * [Bind to a lifecycle](#bind-to-a-lifecycle)\n  * [Additional Build Artifacts](#additional-build-artifacts)\n* [Multi Module Projects](#multi-module-projects)\n* [Extended Usage](#extended-usage)\n  * [Configuration Properties](#configuration-properties)\n  * [Global Jib Configuration](#global-jib-configuration)\n  * [Example](#example)\n  * [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image)\n  * [Authentication Methods](#authentication-methods)\n    * [Using Docker configuration files](#using-docker-configuration-files)\n    * [Using Docker Credential Helpers](#using-docker-credential-helpers)\n    * [Using Specific Credentials](#using-specific-credentials)\n    * [Using Maven Settings](#using-maven-settings)\n  * [Custom Container Entrypoint](#custom-container-entrypoint)\n  * [Jib Extensions](#jib-extensions)\n  * [WAR Projects](#war-projects)\n  * [Skaffold Integration](#skaffold-integration)\n* [Need Help?](#need-help)\n* [Community](#community)\n\n## Quickstart\n\nYou can containerize your application easily with one command:\n\n```shell\nmvn compile com.google.cloud.tools:jib-maven-plugin:3.5.1:build -Dimage=<MY IMAGE>\n```\n\nThis builds and pushes a container image for your application to a container registry. *If you encounter authentication issues, see [Authentication Methods](#authentication-methods).*\n\nTo build to a Docker daemon, use:\n\n```shell\nmvn compile com.google.cloud.tools:jib-maven-plugin:3.5.1:dockerBuild\n```\n\nIf you would like to set up Jib as part of your Maven build, follow the guide below.\n\n### Setup\n\nIn your Maven Java project, add the plugin to your `pom.xml`:\n\n```xml\n<project>\n  ...\n  <build>\n    <plugins>\n      ...\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>3.5.1</version>\n        <configuration>\n          <to>\n            <image>myimage</image>\n          </to>\n        </configuration>\n      </plugin>\n      ...\n    </plugins>\n  </build>\n  ...\n</project>\n```\n\n### Configuration\n\nConfigure the plugin by setting the image to push to:\n\n#### Using [Google Container Registry (GCR)](https://cloud.google.com/container-registry/)...\n\n*Make sure you have the [`docker-credential-gcr` command line tool](https://cloud.google.com/container-registry/docs/advanced-authentication#docker_credential_helper). Jib automatically uses `docker-credential-gcr` for obtaining credentials. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `gcr.io/my-gcp-project/my-app`, the configuration would be:\n\n```xml\n<configuration>\n  <to>\n    <image>gcr.io/my-gcp-project/my-app</image>\n  </to>\n</configuration>\n```\n\n#### Using [Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr/)...\n\n*Make sure you have the [`docker-credential-ecr-login` command line tool](https://github.com/awslabs/amazon-ecr-credential-helper). Jib automatically uses `docker-credential-ecr-login` for obtaining credentials. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `aws_account_id.dkr.ecr.region.amazonaws.com/my-app`, the configuration would be:\n\n```xml\n<configuration>\n  <to>\n    <image>aws_account_id.dkr.ecr.region.amazonaws.com/my-app</image>\n  </to>\n</configuration>\n```\n\n#### Using [Docker Hub Registry](https://hub.docker.com/)...\n\n*Make sure you have a [docker-credential-helper](https://github.com/docker/docker-credential-helpers#available-programs) set up. For example, on macOS, the credential helper would be `docker-credential-osxkeychain`. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `my-docker-id/my-app`, the configuration would be:\n\n```xml\n<configuration>\n  <to>\n    <image>docker.io/my-docker-id/my-app</image>\n  </to>\n</configuration>\n```\n\n#### Using [JFrog Container Registry (JCR)](https://jfrog.com/container-registry) or [JFrog Artifactory](https://jfrog.com/help/r/jfrog-artifactory-documentation/getting-started-with-artifactory-as-a-docker-registry)...\n\n*Make sure you have a [docker-credential-helper](https://github.com/docker/docker-credential-helpers#available-programs) set up. For example, on macOS, the credential helper would be `docker-credential-osxkeychain`. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `my-company-docker-local.jfrog.io/my-app`, the configuration would be:\n\n```xml\n<configuration>\n  <to>\n    <image>my-company-docker-local.jfrog.io/my-app</image>\n  </to>\n</configuration>\n```\n\n#### Using [Azure Container Registry (ACR)](https://azure.microsoft.com/en-us/services/container-registry/)...\n\n*Make sure you have a [`ACR Docker Credential Helper`](https://github.com/Azure/acr-docker-credential-helper) installed and set up. For example, on Windows, the credential helper would be `docker-credential-acr-windows`. See [Authentication Methods](#authentication-methods) for other ways of authenticating.*\n\nFor example, to build the image `my_acr_name.azurecr.io/my-app`, the configuration would be:\n\n```xml\n<configuration>\n  <to>\n    <image>my_acr_name.azurecr.io/my-app</image>\n  </to>\n</configuration>\n```\n\n### Build your image\n\nBuild your container image with:\n\n```shell\nmvn compile jib:build\n```\n\nSubsequent builds are much faster than the initial build.\n\n*Having trouble? Let us know by [submitting an issue](/../../issues/new), contacting us on [Gitter](https://gitter.im/google/jib), or posting to the [Jib users forum](https://groups.google.com/forum/#!forum/jib-users).*\n\n#### Build to Docker daemon\n\nJib can also build your image directly to a Docker daemon. This uses the `docker` command line tool and requires that you have `docker` available on your `PATH`.\n\n```shell\nmvn compile jib:dockerBuild\n```\n\nIf you are using [`minikube`](https://github.com/kubernetes/minikube)'s remote Docker daemon, make sure you [set up the correct environment variables](https://minikube.sigs.k8s.io/docs/handbook/pushing/#1-pushing-directly-to-the-in-cluster-docker-daemon-docker-env) to point to the remote daemon:\n\n```shell\neval $(minikube docker-env)\nmvn compile jib:dockerBuild\n```\n\nAlternatively, you can set environment variables in the Jib configuration. See [`dockerClient`](#dockerclient-object) for more configuration options.\n\n#### Build an image tarball\n\nYou can build and save your image to disk as a tarball with:\n\n```shell\nmvn compile jib:buildTar\n```\n\nThis builds and saves your image to `target/jib-image.tar`, which you can load into docker with:\n\n```shell\ndocker load --input target/jib-image.tar\n```\n\n### Bind to a lifecycle\n\nYou can also bind `jib:build` to a Maven lifecycle, such as `package`, by adding the following execution to your `jib-maven-plugin` definition:\n\n```xml\n<plugin>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>jib-maven-plugin</artifactId>\n  ...\n  <executions>\n    <execution>\n      <phase>package</phase>\n      <goals>\n        <goal>build</goal>\n      </goals>\n    </execution>\n  </executions>\n</plugin>\n```\n\nThen, you can build your container image by running:\n\n```shell\nmvn package\n```\n\n### Additional Build Artifacts\n\nAs part of an image build, Jib also writes out the _image digest_ and the _image ID_. By default, these are written out to `target/jib-image.digest` and `target/jib-image.id` respectively, but the locations can be configured using the `<outputPaths><digest>` and `<outputPaths><imageId>` configuration properties. See [Extended Usage](#outputpaths-object) for more details.\n\n## Multi Module Projects\n\nSpecial handling of project dependencies is recommended when building complex\nmulti module projects. See [Multi Module Example](https://github.com/GoogleContainerTools/jib/tree/master/examples/multi-module) for detailed information.\n\n## Extended Usage\n\nExtended configuration options provide additional options for customizing the image build.\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`to` | [`to`](#to-object) | *Required* | Configures the target image to build your application to.\n`from` | [`from`](#from-object) | See [`from`](#from-object) | Configures the base image to build your application on top of.\n`container` | [`container`](#container-object) | See [`container`](#container-object) | Configures the container that is run from your image.\n`extraDirectories` | [`extraDirectories`](#extradirectories-object) | See [`extraDirectories`](#extradirectories-object) | Configures the directories used to add arbitrary files to the image.\n`outputPaths` | [`outputPaths`](#outputpaths-object) | See [`outputPaths`](#outputpaths-object) | Configures the locations of additional build artifacts generated by Jib.\n`dockerClient` | [`dockerClient`](#dockerclient-object) | See [`dockerClient`](#dockerclient-object) | Configures Docker for building to/from the Docker daemon.\n`skaffold` | [`skaffold`](#skaffold-integration) | See [`skaffold`](#skaffold-integration) | Configures the internal skaffold goals. This configuration should only be used when integrating with [`skaffold`](#skaffold-integration). |\n`containerizingMode` | string | `exploded` | If set to `packaged`, puts the JAR artifact built at `${project.build.directory}/${project.build.finalName}.jar` (the default location where many JAR-building plugins put a JAR registered as a main artifact, such as the Maven JAR Plugin) into the final image. If set to `exploded` (default), containerizes individual `.class` files and resources files.\n`allowInsecureRegistries` | boolean | `false` | If set to true, Jib ignores HTTPS certificate errors and may fall back to HTTP as a last resort. Leaving this parameter set to `false` is strongly recommended, since HTTP communication is unencrypted and visible to others on the network, and insecure HTTPS is no better than plain HTTP. [If accessing a registry with a self-signed certificate, adding the certificate to your Java runtime's trusted keys](https://github.com/GoogleContainerTools/jib/tree/master/docs/self_sign_cert.md) may be an alternative to enabling this option.\n`skip` | boolean | `false` | If set to true, Jib execution is skipped (useful for multi-module projects). This can also be specified via the `-Djib.skip` command line option.\n\n<a name=\"from-object\"></a>`from` is an object with the following properties:\n\nProperty | Type | Default                                                   | Description\n--- | --- |-----------------------------------------------------------| ---\n`image` | string | `eclipse-temurin:{8,11,17,21,25}-jre` (or `jetty` for WAR) | The image reference for the base image. The source type can be specified using a [special type prefix](#setting-the-base-image).\n`auth` | [`auth`](#auth-object) | *None*                                                    | Specifies credentials directly (alternative to `credHelper`).\n`credHelper` | string | *None*                                                    | Specifies a credential helper that can authenticate pulling the base image. This parameter can either be configured as an absolute path to the credential helper executable or as a credential helper suffix (following `docker-credential-`).\n`platforms` | list | See [`platform`](#platform-object)                        | Configures platforms of base images to select from a manifest list.\n\n<a name=\"to-object\"></a>`to` is an object with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`image` | string | *Required* | The image reference for the target image. This can also be specified via the `-Dimage` command line option. If the tag is not present here `:latest` is implied.\n`auth` | [`auth`](#auth-object) | *None* | Specifies credentials directly (alternative to `credHelper`).\n`credHelper` | string | *None* | Specifies a credential helper that can authenticate pushing the target image. This parameter can either be configured as an absolute path to the credential helper executable or as a credential helper suffix (following `docker-credential-`).\n`tags` | list | *None* | Additional tags to push to.\n\n<a name=\"auth-object\"></a>`auth` is an object with the following properties (see [Using Specific Credentials](#using-specific-credentials)):\n\nProperty | Type\n--- | ---\n`username` | string\n`password` | string\n\n<a name=\"platform-object\"></a>`platform` is an object with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`architecture` | string | `amd64` | The architecture of a base image to select from a manifest list.\n`os` | string | `linux` | The OS of a base image to select from a manifest list.\n\nSee [How do I specify a platform in the manifest list (or OCI index) of a base image?](../docs/faq.md#how-do-i-specify-a-platform-in-the-manifest-list-or-oci-index-of-a-base-image) for examples.\n\n<a name=\"container-object\"></a>`container` is an object with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`appRoot` | string | `/app` | The root directory on the container where the app's contents are placed. Particularly useful for WAR-packaging projects to work with different Servlet engine base images by designating where to put exploded WAR contents; see [WAR usage](#war-projects) as an example.\n`args` | list | *None* | Additional program arguments appended to the command to start the container (similar to Docker's [CMD](https://docs.docker.com/engine/reference/builder/#cmd) instruction in relation with [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint)). In the default case where you do not set a custom `entrypoint`, this parameter is effectively the arguments to the main method of your Java application.\n`creationTime` | string | `EPOCH` | Sets the container creation time. (Note that this property does not affect the file modification times, which are configured using `<filesModificationTime>`.) The value can be `EPOCH` to set the timestamps to Epoch (default behavior), `USE_CURRENT_TIMESTAMP` to forgo reproducibility and use the real creation time, or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`.\n`entrypoint` | list | *None* | The command to start the container with (similar to Docker's [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint) instruction). If set, then `jvmFlags`, `mainClass`, `extraClasspath`, and `expandClasspathDependencies` are ignored. You may also set `<entrypoint>INHERIT</entrypoint>` (`<entrypoint><entry>INHERIT</entry></entrypoint>` in old Maven versions) to indicate that the `entrypoint` and `args` should be inherited from the base image.\\*\n`environment` | map | *None* | Key-value pairs for setting environment variables on the container (similar to Docker's [ENV](https://docs.docker.com/engine/reference/builder/#env) instruction).\n`extraClasspath` | list | *None* | Additional paths in the container to prepend to the computed Java classpath.\n`expandClasspathDependencies` | boolean | `false` | <ul><li>Java 8 *or* Jib < 3.1: When set to true, does not use a wildcard (for example, `/app/lib/*`) for dependency JARs in the default Java runtime classpath but instead enumerates the JARs. Has the effect of preserving the classpath loading order as defined by the Maven project.</li><li>Java >= 9 *and* Jib >= 3.1: The option has no effect. Jib *always* enumerates the dependency JARs. This is achieved by [creating and using an argument file](#custom-container-entrypoint) for the `--class-path` JVM argument.</li></ul>\n`filesModificationTime` | string | `EPOCH_PLUS_SECOND` | Sets the modification time (last modified time) of files in the image put by Jib. (Note that this does not set the image creation time, which can be set using `<creationTime>`.) The value should either be `EPOCH_PLUS_SECOND` to set the timestamps to Epoch + 1 second (default behavior), or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`.\n`format` | string | `Docker` | Use `OCI` to build an [OCI container image](https://www.opencontainers.org/).\n`jvmFlags` | list | *None* | Additional flags to pass into the JVM when running your application.\n`labels` | map | *None* | Key-value pairs for applying image metadata (similar to Docker's [LABEL](https://docs.docker.com/engine/reference/builder/#label) instruction).\n`mainClass` | string | *Inferred*\\*\\* | The main class to launch the application from.\n`ports` | list | *None* | Ports that the container exposes at runtime (similar to Docker's [EXPOSE](https://docs.docker.com/engine/reference/builder/#expose) instruction).\n`user` | string | *None* | The user and group to run the container as. The value can be a username or UID along with an optional groupname or GID. The following are all valid: `user`, `uid`, `user:group`, `uid:gid`, `uid:group`, `user:gid`.\n`volumes` | list | *None* | Specifies a list of mount points on the container.\n`workingDirectory` | string | *None* | The working directory in the container.\n\n<a name=\"extradirectories-object\"></a>`extraDirectories` is an object with the following properties (see [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image)):\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`paths` | list | `[(project-dir)/src/main/jib]` | List of [`path`](#path-object) objects and/or extra directory paths. Can be absolute or relative to the project root.\n`permissions` | list | *None* | Maps file paths (glob patterns) on container to Unix permissions. (Effective only for files added from extra directories.) If not configured, permissions default to \"755\" for directories and \"644\" for files. See [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) for an example.\n\n<a name=\"path-object\"></a>`path` is an object with the following properties (see [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image)):\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`from` | file | `[(project-dir)/src/main/jib]` | The source directory. Can be absolute or relative to the project root.\n`into` | string | `/` | The absolute unix path on the container to copy the extra directory contents into.\n`includes` | list | *None* | Glob patterns for including files. See [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) for an example.\n`excludes` | list | *None* | Glob patterns for excluding files. See [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) for an example.\n\n<a name=\"outputpaths-object\"></a>`outputPaths` is an object with the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`tar` | string | `(project-dir)/target/jib-image.tar` | The path of the tarball generated by `jib:buildTar`. Relative paths are resolved relative to the project root.\n`digest` | string | `(project-dir)/target/jib-image.digest` | The path of the image digest written out during the build. Relative paths are resolved relative to the project root.\n`imageId` | string | `(project-dir)/target/jib-image.id` | The path of the image ID written out during the build. Relative paths are resolved relative to the project root.\n`imageJson` | string | `(project-dir)/target/jib-image.json` | The path of the image metadata json file written out during the build. Relative paths are resolved relative to the project root.\n\n<a name=\"dockerclient-object\"></a>`dockerClient` is an object used to configure Docker when building to/from the Docker daemon. It has the following properties:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`executable` | string | `docker` | Sets the path to the Docker executable that is called to load the image into the Docker daemon. **Please note**: Users are responsible for ensuring that the Docker path passed in is valid and has the right permissions to be executed.\n`environment` | map | *None* | Sets environment variables used by the Docker executable.\n\n#### Configuration Properties\n\nEach of these parameters is configurable via commandline using properties. Jib's properties follow the same naming convention as the configuration parameters, with each level separated by dots (i.e. `-Djib.parameterName[.nestedParameter.[...]]=value`). Some examples are below:\n```shell\nmvn compile jib:build \\\n    -Djib.to.image=myregistry/myimage:latest \\\n    -Djib.to.auth.username=$USERNAME \\\n    -Djib.to.auth.password=$PASSWORD\n\nmvn compile jib:dockerBuild \\\n    -Djib.dockerClient.executable=/path/to/docker \\\n    -Djib.container.environment=key1=\"value1\",key2=\"value2\" \\\n    -Djib.container.args=arg1,arg2,arg3\n```\n\nThe following table contains additional properties that are not available as build configuration parameters:\n\nProperty | Type | Default | Description\n--- | --- | --- | ---\n`jib.httpTimeout` | int | `20000` | HTTP connection/read timeout for registry interactions, in milliseconds. Use a value of `0` for an infinite timeout.\n`jib.useOnlyProjectCache` | boolean | `false` | If set to true, Jib does not share a cache between different Maven projects (i.e. `jib.baseImageCache` defaults to `[project dir]/target/jib-cache` instead of `[user cache home]/google-cloud-tools-java/jib`).\n`jib.baseImageCache` | string | *Platform-dependent*\\*\\*\\* | Sets the directory to use for caching base image layers. This cache can (and should) be shared between multiple images.\n`jib.applicationCache` | string | `[project dir]/target/jib-cache` | Sets the directory to use for caching application layers. This cache can be shared between multiple images.\n`jib.console` | string | *None* | If set to `plain`, Jib will print plaintext log messages rather than display a progress bar during the build.\n\n*\\* If you configure `args` while `entrypoint` is set to `'INHERIT'`, the configured `args` value will take precedence over the CMD propagated from the base image.*\n\n*\\*\\* Uses the main class defined in the `jar` task or tries to find a valid main class.*\n\n*\\*\\*\\* The default base image cache is in the following locations on each platform:*\n * *Linux: `[cache root]/google-cloud-tools-java/jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/.cache/` if not set)*\n * *Mac: `[cache root]/Google/Jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/Library/Caches/` if not set)*\n * *Windows: `[cache root]\\Google\\Jib\\Cache`, where `[cache root]` is `%XDG_CACHE_HOME%` (`%LOCALAPPDATA%` if not set)*\n\n### Global Jib Configuration\n\nSome options can be set in the global Jib configuration file. The file is at the following locations on each platform:\n\n* *Linux: `[config root]/google-cloud-tools-java/jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/.config/` if not set)*\n* *Mac: `[config root]/Google/Jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/Library/Preferences/` if not set)*\n* *Windows: `[config root]\\Google\\Jib\\Config\\config.json`, where `[config root]` is `%XDG_CONFIG_HOME%` (`%LOCALAPPDATA%` if not set)*\n\n#### Properties\n\n* `disableUpdateCheck`: when set to true, disables the periodic up-to-date version check.\n* `registryMirrors`: a list of mirror settings for each base image registry. In the following example, if the base image configured in Jib is for a Docker Hub image, then `mirror.gcr.io`, `localhost:5000`, and the Docker Hub (`registry-1.docker.io`) are tried in order until Jib can successfully pull a base image.\n\n```json\n{\n  \"disableUpdateCheck\": false,\n  \"registryMirrors\": [\n    {\n      \"registry\": \"registry-1.docker.io\",\n      \"mirrors\": [\"mirror.gcr.io\", \"localhost:5000\"]\n    },\n    {\n      \"registry\": \"quay.io\",\n      \"mirrors\": [\"private-mirror.test.com\"]\n    }\n  ]\n}\n```\n**Note about `mirror.gcr.io`**: it is _not_ a Docker Hub mirror but a cache. It caches [frequently-accessed public Docker Hub images](https://cloud.google.com/container-registry/docs/pulling-cached-images), and it's often possible that your base image does not exist in `mirror.gcr.io`. In that case, Jib will have to fall back to use Docker Hub.\n\n### Example\n\nIn this configuration, the image:\n* Is built from a base of `openjdk:alpine` (pulled from Docker Hub)\n* Is pushed to `localhost:5000/my-image:built-with-jib`, `localhost:5000/my-image:tag2`, and `localhost:5000/my-image:latest`\n* Runs by calling `java -Dmy.property=example.value -Xms512m -Xdebug -cp app/libs/*:app/resources:app/classes mypackage.MyApp some args`\n* Exposes port 1000 for tcp (default), and ports 2000, 2001, 2002, and 2003 for udp\n* Has two labels (key1:value1 and key2:value2)\n* Is built as OCI format\n\n```xml\n<configuration>\n  <from>\n    <image>openjdk:alpine</image>\n  </from>\n  <to>\n    <image>localhost:5000/my-image:built-with-jib</image>\n    <credHelper>osxkeychain</credHelper>\n    <tags>\n      <tag>tag2</tag>\n      <tag>latest</tag>\n    </tags>\n  </to>\n  <container>\n    <jvmFlags>\n      <jvmFlag>-Dmy.property=example.value</jvmFlag>\n      <jvmFlag>-Xms512m</jvmFlag>\n      <jvmFlag>-Xdebug</jvmFlag>\n    </jvmFlags>\n    <mainClass>mypackage.MyApp</mainClass>\n    <args>\n      <arg>some</arg>\n      <arg>args</arg>\n    </args>\n    <ports>\n      <port>1000</port>\n      <port>2000-2003/udp</port>\n    </ports>\n    <labels>\n      <key1>value1</key1>\n      <key2>value2</key2>\n    </labels>\n    <format>OCI</format>\n  </container>\n</configuration>\n```\n\n### Setting the Base Image\n\nThere are three different types of base images that Jib accepts: an image from a container registry, an image stored in the Docker daemon, or an image tarball on the local filesystem. You can specify which you would like to use by prepending the `<from><image>` configuration with a special prefix, listed below:\n\nPrefix | Example | Type\n--- | --- | ---\n*None* | `openjdk:11-jre` | Pulls the base image from a registry.\n`registry://` | `registry://eclipse-temurin:11-jre` | Pulls the base image from a registry.\n`docker://` | `docker://busybox` | Retrieves the base image from the Docker daemon.\n`tar://` | `tar:///path/to/file.tar` | Uses an image tarball stored at the specified path as the base image. Also accepts relative paths (e.g. `tar://target/jib-image.tar`).\n\n### Adding Arbitrary Files to the Image\n\nYou can add arbitrary, non-classpath files to the image by placing them in a `src/main/jib` directory. This will copy all files within the `jib` folder to the target directory (`/` by default) in the image, maintaining the same structure (e.g. if you have a text file at `src/main/jib/dir/hello.txt`, then your image will contain `/dir/hello.txt` after being built with Jib).\n\nNote that Jib does not follow symbolic links in the container image.  If a symbolic link is present, _it will be removed_ prior to placing the files and directories.\n\nYou can configure different directories by using the `<extraDirectories>` parameter in your `pom.xml`:\n```xml\n<configuration>\n  <!-- Copies files from 'src/main/custom-extra-dir' and '/home/user/jib-extras' instead of 'src/main/jib' -->\n  <extraDirectories>\n    <paths>\n      <!-- Copies from 'src/main/custom-extra-dir' into '/' on the container. -->\n      <path>src/main/custom-extra-dir</path>\n      <!-- Copies from '/home/user/jib-extras' into '/extras' on the container -->\n      <path>\n        <from>/home/user/jib-extras</from>\n        <into>/extras</into>\n      </path>\n    </paths>\n  </extraDirectories>\n</configuration>\n```\n\nAlternatively, the `<extraDirectories>` parameter can be used as an object to set custom extra directories, as well as the extra files' permissions on the container:\n\n```xml\n  <extraDirectories>\n    <paths>src/main/custom-extra-dir</paths> <!-- Copies files from 'src/main/custom-extra-dir' -->\n    <permissions>\n      <permission>\n        <file>/path/on/container/to/fileA</file>\n        <mode>755</mode> <!-- Read/write/execute for owner, read/execute for group/other -->\n      </permission>\n      <permission>\n        <file>/path/to/another/file</file>\n        <mode>644</mode> <!-- Read/write for owner, read-only for group/other -->\n      </permission>\n      <permission>\n        <file>/glob/pattern/**/*.sh</file>\n        <mode>755</mode>\n      </permission>\n    </permissions>\n  </extraDirectories>\n```\n\nYou may also specify the target of the copy and include or exclude files:\n\n```xml\n  <extraDirectories>\n    <paths>\n      <path>\n        // copies the contents of 'src/main/extra-dir' into '/' on the container\n        <from>src/main/extra-dir</from>\n      </path>\n      <path>\n        // copies the contents of 'src/main/another/dir' into '/extras' on the container\n        <from>src/main/another/dir</from>\n        <into>/extras</into>\n      </path>\n      <path>\n        // copies a single-file.xml\n        <from>src/main/resources/xml-files</from>\n        <into>/dest-in-container</into>\n        <includes>single-file.xml</includes>\n      </path>\n      <path>\n        // copies only .txt files except for 'hidden.txt' at the source root\n        <from>build/some-output</from>\n        <into>/txt-files</into>\n        <includes>*.txt,**/*.txt</includes>\n        <excludes>\n          <exclude>hidden.txt</exclude>\n        </excludes>\n      </path>\n    </paths>\n  </extraDirectories>\n```\n\n### Authentication Methods\n\nPushing/pulling from private registries require authorization credentials.\n\n#### Using Docker configuration files\n\n* Jib looks from credentials from `$XDG_RUNTIME_DIR/containers/auth.json`, `$XDG_CONFIG_HOME/containers/auth.json`, `$HOME/.config/containers/auth.json`, `$DOCKER_CONFIG/config.json`, and `$HOME/.docker/config.json`.\n\nSee [this issue](/../../issues/101) and [`man containers-auth.json`](https://www.mankier.com/5/containers-auth.json) for more information about the files.\n\n#### Using Docker Credential Helpers\n\nDocker credential helpers are CLI tools that handle authentication with various registries.\n\nSome common credential helpers include:\n\n* Google Container Registry: [`docker-credential-gcr`](https://cloud.google.com/container-registry/docs/advanced-authentication#docker_credential_helper)\n* AWS Elastic Container Registry: [`docker-credential-ecr-login`](https://github.com/awslabs/amazon-ecr-credential-helper)\n* Docker Hub Registry: [`docker-credential-*`](https://github.com/docker/docker-credential-helpers)\n* Azure Container Registry: [`docker-credential-acr-*`](https://github.com/Azure/acr-docker-credential-helper)\n\nConfigure credential helpers to use by specifying them as a `credHelper` for their respective image.\n\n*Example configuration:*\n```xml\n<configuration>\n  ...\n  <from>\n    <image>aws_account_id.dkr.ecr.region.amazonaws.com/my-base-image</image>\n    <credHelper>ecr-login</credHelper>\n  </from>\n  <to>\n    <image>gcr.io/my-gcp-project/my-app</image>\n    <credHelper>gcr</credHelper>\n  </to>\n  ...\n</configuration>\n```\n\n#### Using Specific Credentials\n\nYou can specify credentials directly in the `<auth>` parameter for the `from` and/or `to` images. In the example below, `to` credentials are retrieved from the `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` environment variables.\n\n```xml\n<configuration>\n  ...\n  <from>\n    <image>aws_account_id.dkr.ecr.region.amazonaws.com/my-base-image</image>\n    <auth>\n      <username>my_username</username>\n      <password>my_password</password>\n    </auth>\n  </from>\n  <to>\n    <image>gcr.io/my-gcp-project/my-app</image>\n    <auth>\n      <username>${env.REGISTRY_USERNAME}</username>\n      <password>${env.REGISTRY_PASSWORD}</password>\n    </auth>\n  </to>\n  ...\n</configuration>\n```\n\nAlternatively, you can specify credentials via commandline using the following system properties.\n\nProperty | Description\n--- | ---\n`-Djib.from.auth.username` | Username for base image registry.\n`-Djib.from.auth.password` | Password for base image registry.\n`-Djib.to.auth.username` | Username for target image registry.\n`-Djib.to.auth.password` | Password for target image registry.\n\ne.g. `mvn compile jib:build -Djib.to.auth.username=user -Djib.to.auth.password=pass`\n\n**Note:** This method of authentication should be used only as a last resort, as it is insecure to make your password visible in plain text. Note that often cloud registries (for example, Google GCR, Amazon ECR, and Azure ACR) do not accept \"user credentials\" (such as Gmail account name and password) but require different forms of credentials. For example, you may use [`oauth2accesstoken` or `_json_key`](https://cloud.google.com/container-registry/docs/advanced-authentication) as the username for GCR, and [`AWS`](https://serverfault.com/questions/1004915/what-is-the-proper-way-to-log-in-to-ecr) for ECR. For ACR, you may use a [_service principle_](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-service-principal).\n\n#### Using Maven Settings\n\nRegistry credentials can be added to your [Maven settings](https://maven.apache.org/settings.html). These credentials will be used if credentials could not be found in any specified Docker credential helpers.\n\nIf you're considering putting credentials in Maven, we highly *recommend* using [maven password encryption](https://maven.apache.org/guides/mini/guide-encryption.html).\n\n*Example `settings.xml`:*\n```xml\n<settings>\n  ...\n  <servers>\n    ...\n    <server>\n      <id>MY_REGISTRY</id>\n      <username>MY_USERNAME</username>\n      <password>{MY_SECRET}</password>\n    </server>\n  </servers>\n</settings>\n```\n\n* The `id` field should be the registry server these credentials are for.\n* We *do not* recommend putting your raw password in `settings.xml`.\n\n\n### Custom Container Entrypoint\n\nIf you don't set `<container><entrypoint>`, the default container entrypoint to launch your app will be basically `java -cp <runtime classpath> <app main class>`. (The final `java` command can be further configured by setting `<container>{<jvmFlags>|<args>|<extraClasspath>|<mainClass>|<expandClasspathDependencies>}`.)\n\nSometimes, you'll want to set a custom entrypoint to use a shell to wrap the `java` command. For example, to let `sh` or `bash` [expand environment variables](https://stackoverflow.com/a/59361658/1701388), or to have more sophisticated logic to construct a launch command. (Note, however, that running a command with a shell forks a new child process unless you run it with `exec` like `sh -c \"exec java ...\"`. Whether to run the JVM process as PID 1 or a child process of a PID-1 shell is a [decision you should make carefully](https://github.com/GoogleContainerTools/distroless/issues/550#issuecomment-791610603).) In this scenario, you will want to have a way inside a shell script to reliably know the default runtime classpath and the main class that Jib would use by default. To help this, Jib >= 3.1 creates two JVM argument files under `/app` (the default app root) inside the built image.\n\n- `/app/jib-classpath-file`: runtime classpath that Jib would use for default app launch\n- `/app/jib-main-class-file`: main class\n\nTherefore, *for example*, the following commands will be able to launch your app:\n\n- (Java 9+) `java -cp @/app/jib-classpath-file @/app/jib-main-class-file`\n- (with shell) `java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )`\n\n\n### Jib Extensions\n\nThe Jib build plugins have an extension framework that enables anyone to easily extend Jib's behavior to their needs. We maintain select [first-party](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party) plugins for popular use cases like [fine-grained layer control](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-layer-filter-extension-gradle), builds a [GraalVM native image](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-native-image-extension-maven), and [Quarkus support](https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-quarkus-extension-gradle), but anyone can write and publish an extension. Check out the [jib-extensions](https://github.com/GoogleContainerTools/jib-extensions) repository for more information.\n\n\n### WAR Projects\n\nJib also containerizes WAR projects. If the Maven project uses [the `war`-packaging type](https://maven.apache.org/plugins/maven-war-plugin/index.html), Jib will by default use [`jetty`](https://hub.docker.com/_/jetty) as a base image to deploy the project WAR. No extra configuration is necessary other than having the packaging type to `war`.\n\nNote that Jib will work slightly differently for WAR projects from JAR projects:\n   - `<container><mainClass>` and `<container><jvmFlags>` are ignored.\n   - The WAR will be exploded into `/var/lib/jetty/webapps/ROOT`, which is the expected WAR location for the Jetty base image.\n\nTo use a different Servlet engine base image, you can customize `<container><appRoot>`, `<container><entrypoint>`, and `<container><args>`. If you do not set `entrypoint` or `args`, Jib will inherit the `ENTRYPOINT` and `CMD` of the base image, so in many cases, you may not need to configure them. However, you will most likely have to set `<container><appRoot>` to a proper location depending on the base image. Here is an example of using a Tomcat image:\n\n```xml\n<configuration>\n  <from>\n    <image>tomcat:8.5-jre8-alpine</image>\n  </from>\n  <container>\n    <!--\n      For demonstration only: this directory in the base image contains a Tomcat default\n      app (welcome page), so you may first want to delete this directory in the base image.\n    -->\n    <appRoot>/usr/local/tomcat/webapps/ROOT</appRoot>\n  </container>\n</configuration>\n```\nWhen specifying a [`jetty`](https://hub.docker.com/_/jetty) image yourself with `<from><image>`, you may run into an issue ([#3204](https://github.com/GoogleContainerTools/jib/issues/3204)) and need to override the entrypoint.\n```xml\n<configuration>\n  <from>\n    <image>jetty:11.0.2-jre11</image>\n  </from>\n  <container>\n    <entrypoint>java,-jar,/usr/local/jetty/start.jar</entrypoint>\n  </container>\n</configuration>\n```\n\n\n### Skaffold Integration\n\nJib is an included builder in [Skaffold](https://github.com/GoogleContainerTools/skaffold). Jib passes build information to skaffold through special internal goals so that skaffold understands when it should rebuild or synchronize files. For complex builds, the defaults may not be sufficient, so the jib plugin provides a `skaffold` configuration object which exposes:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`watch` | [`watch`](#skaffold-watch-object) | *None* | Additional configuration for file watching\n`sync` | [`sync`](#skaffold-sync-object) | *None* | Additional configuration for file synchronization\n\n<a name=\"skaffold-watch-object\"></a>`watch` is an object with the following properties:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`buildIncludes` | `List<String>` | *None* | Additional build files that skaffold should watch\n`includes` | `List<String>` | *None* | Additional project files or directories that skaffold should watch\n`excludes` | `List<String>` | *None* | Files and directories that skaffold should not watch\n\n<a name=\"skaffold-sync-object\"></a>`sync` is an object with the following properties:\n\nField | Type | Default | Description\n--- | --- | --- | ---\n`excludes` | `List<String>` | *None* | Files and directories that skaffold should not sync\n\n## <a name=frequently-asked-questions-faq></a>Need Help?\n\nA lot of questions are already answered!\n\n* [Frequently Asked Questions (FAQ)](../docs/faq.md)\n* [Stack Overflow](https://stackoverflow.com/questions/tagged/jib)\n* [GitHub issues](https://github.com/GoogleContainerTools/jib/issues)\n\n_For usage questions, please ask them on Stack Overflow._\n\n## Privacy\n\nSee the [Privacy page](docs/privacy.md).\n\n## Upcoming Features\n\nSee [Milestones](https://github.com/GoogleContainerTools/jib/milestones) for planned features. [Get involved with the community](https://github.com/GoogleContainerTools/jib/tree/master#get-involved-with-the-community) for the latest updates.\n\n## Community\n\nSee the [Jib project README](/../../#community).\n\n## Disclaimer\n\nThis is not an officially supported Google product.\n"
  },
  {
    "path": "jib-maven-plugin/build.gradle",
    "content": "plugins {\n  id 'io.freefair.maven-plugin'\n  id 'net.researchgate.release'\n  id 'maven-publish'\n  id 'eclipse'\n}\n\n// only maven specific dependencies should be versioned, everything else should be defined by constrains in\n// parent build.gradle\ndependencies {\n  sourceProject project(':jib-core')\n  sourceProject project(':jib-plugins-common')\n  ensureNoProjectDependencies()\n\n  implementation dependencyStrings.MAVEN_EXTENSION\n\n  implementation dependencyStrings.MAVEN_API\n  implementation dependencyStrings.MAVEN_CORE\n\n  // compileOnly + testImplementation equivalent to \"provided\"\n  compileOnly dependencyStrings.MAVEN_PLUGIN_ANNOTATIONS\n  // needed to suppress \"unknown enum constant\" warnings\n  testImplementation dependencyStrings.MAVEN_PLUGIN_ANNOTATIONS\n\n  // maven-plugin-testing-harness pulls in conflicting implementations of DefaultPlexusContainer\n  // (sisu (correct) vs default-plexus-container (wrong)) so ensure this is first in the test classpath\n  testImplementation dependencyStrings.SISU_PLEXUS\n  testImplementation dependencyStrings.MAVEN_TESTING_HARNESS\n\n  testImplementation dependencyStrings.JUNIT\n  testImplementation dependencyStrings.TRUTH\n  testImplementation dependencyStrings.TRUTH8\n  testImplementation dependencyStrings.MOCKITO_CORE\n  testImplementation dependencyStrings.SLF4J_API\n  testImplementation dependencyStrings.SYSTEM_RULES\n\n  testImplementation dependencyStrings.MAVEN_VERIFIER\n  testImplementation dependencyStrings.MAVEN_COMPAT\n  testImplementation dependencyStrings.SLF4J_SIMPLE\n\n  integrationTestImplementation dependencyStrings.JBCRYPT\n\n  testImplementation project(path:':jib-plugins-common', configuration:'tests')\n  integrationTestImplementation project(path:':jib-core', configuration:'integrationTests')\n}\n\n/* TESTING */\ntest.dependsOn publishToMavenLocal\nintegrationTest.dependsOn publishToMavenLocal\n/* TESTING */\n\n/* RELEASE */\nconfigureMavenRelease()\n\npublishing {\n  publications {\n    mavenJava(MavenPublication) {\n      pom {\n        name = 'Jib'\n        description = 'A Maven plugin for building container images for your Java applications.'\n      }\n    }\n  }\n}\n\n\nrelease {\n  tagTemplate = 'v$version-maven'\n  ignoredSnapshotDependencies = [\n    'com.google.cloud.tools:jib-core',\n    'com.google.cloud.tools:jib-plugins-common',\n  ]\n  git {\n    requireBranch = /^maven-release-v\\d+.*$/  //regex\n  }\n}\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\neclipse.classpath.file.whenMerged {\n  entries.each {\n    if (it.path == 'src/test/resources') {\n      it.excludes += 'maven/projects/'\n    }\n  }\n}\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-maven-plugin/gradle.properties",
    "content": "version = 3.5.2-SNAPSHOT\n"
  },
  {
    "path": "jib-maven-plugin/kokoro/release_build.sh",
    "content": "#!/bin/bash\n\n# Fail on any error.\nset -o errexit\n# Display commands to stderr.\nset -o xtrace\n\n# Append to JAVA_TOOL_OPTIONS to suppress warnings from kokoro container os.\nif [ \"${KOKORO_JOB_CLUSTER}\" = \"GCP_UBUNTU_DOCKER\" ]; then\n  JAVA_TOOL_OPTIONS=\"${JAVA_TOOL_OPTIONS} -Xlog:os+container=error\"\nfi\n\ncd github/jib\n./gradlew :jib-maven-plugin:prepareRelease\n"
  },
  {
    "path": "jib-maven-plugin/scripts/update_gcs_latest.sh",
    "content": "#!/bin/bash -\n# Usage: ./jib-maven-plugin/scripts/update_gcs_latest.sh <release version>\n\nset -o errexit\n\nEchoRed() {\n\techo \"$(tput setaf 1; tput bold)$1$(tput sgr0)\"\n}\nEchoGreen() {\n\techo \"$(tput setaf 2; tput bold)$1$(tput sgr0)\"\n}\n\nDie() {\n\tEchoRed \"$1\"\n\texit 1\n}\n\n# Usage: CheckVersion <version>\nCheckVersion() {\n    [[ $1 =~ ^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z]+)?$ ]] || Die \"Version: $1 not in ###.###.###[-XXX] format.\"\n}\n\n[ $# -ne 1 ] && Die \"Usage: ./jib-maven-plugin/scripts/update_gcs_latest.sh <release version>\"\n\nCheckVersion $1\n\nversionString=\"{\\\"latest\\\":\\\"$1\\\"}\"\ndestination=\"gs://jib-versions/jib-maven\"\n\necho $versionString > jib-maven\ngsutil cp jib-maven $destination\ngsutil acl ch -u allUsers:READ $destination\nrm jib-maven\n\ngcsResult=$(curl https://storage.googleapis.com/jib-versions/jib-maven)\nif [ \"$gcsResult\" == \"$versionString\" ]\nthen\n  EchoGreen \"Version updated successfully\"\nelse\n  Die \"Version update failed\"\nfi"
  },
  {
    "path": "jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.Command;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Assume;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Integration tests for {@link BuildDockerMojo}. */\npublic class BuildDockerMojoIntegrationTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule public static final TestProject emptyTestProject = new TestProject(\"empty\");\n\n  @ClassRule\n  public static final TestProject defaultTargetTestProject = new TestProject(\"default-target\");\n\n  private static void buildToDockerDaemon(TestProject project, String imageReference, String pomXml)\n      throws VerificationException, DigestException, IOException {\n    Verifier verifier = new Verifier(project.getProjectRoot().toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", imageReference);\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"--file=\" + pomXml);\n    verifier.executeGoal(\"package\");\n\n    verifier.executeGoal(\"jib:dockerBuild\");\n    verifier.verifyErrorFreeLog();\n\n    BuildImageMojoIntegrationTest.readDigestFile(\n        project.getProjectRoot().resolve(\"target/jib-image.digest\"));\n  }\n\n  /**\n   * Builds and runs jib:buildDocker on a project at {@code projectRoot} pushing to {@code\n   * imageReference}.\n   */\n  private static String buildToDockerDaemonAndRun(TestProject project, String imageReference)\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    buildToDockerDaemon(project, imageReference, \"pom.xml\");\n\n    String dockerInspectVolumes =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Volumes}}'\", imageReference).run();\n    String dockerInspectExposedPorts =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.ExposedPorts}}'\", imageReference)\n            .run();\n    String dockerInspectLabels =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Labels}}'\", imageReference).run();\n    String history = new Command(\"docker\", \"history\", imageReference).run();\n\n    MatcherAssert.assertThat(\n        dockerInspectVolumes, CoreMatchers.containsString(\"\\\"/var/log\\\":{},\\\"/var/log2\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectExposedPorts,\n        CoreMatchers.containsString(\n            \"\\\"1000/tcp\\\":{},\\\"2000/udp\\\":{},\\\"2001/udp\\\":{},\\\"2002/udp\\\":{},\\\"2003/udp\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectLabels,\n        CoreMatchers.containsString(\"\\\"key1\\\":\\\"value1\\\",\\\"key2\\\":\\\"value2\\\"\"));\n\n    return new Command(\"docker\", \"run\", \"--rm\", imageReference).run();\n  }\n\n  @Test\n  public void testExecute_simple()\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrw-r--r--\\nrw-r--r--\\nfoo\\ncat\\n\"\n            + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\",\n        buildToDockerDaemonAndRun(simpleTestProject, targetImage));\n    Assert.assertEquals(\n        \"1970-01-01T00:00:00Z\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Created}}\", targetImage).run().trim());\n  }\n\n  @Test\n  public void testExecute_simple_extraDirectoriesFiltering()\n      throws DigestException, IOException, InterruptedException, VerificationException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n    buildToDockerDaemon(simpleTestProject, targetImage, \"pom-extra-dirs-filtering.xml\");\n    String output =\n        new Command(\"docker\", \"run\", \"--rm\", \"--entrypoint=ls\", targetImage, \"-1R\", \"/extras\")\n            .run();\n\n    //   /extras/cat.txt\n    //   /extras/foo\n    //   /extras/sub/\n    //   /extras/sub/a.json\n    assertThat(output).isEqualTo(\"/extras:\\ncat.txt\\nfoo\\nsub\\n\\n/extras/sub:\\na.json\\n\");\n  }\n\n  @Test\n  public void testExecute_dockerClient()\n      throws VerificationException, IOException, InterruptedException {\n    Assume.assumeFalse(System.getProperty(\"os.name\").startsWith(\"Windows\"));\n    new Command(\n            \"chmod\", \"+x\", simpleTestProject.getProjectRoot().resolve(\"mock-docker.sh\").toString())\n        .run();\n\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"--file=pom-dockerclient.xml\");\n    verifier.addCliOption(\"--debug\");\n    verifier.executeGoal(\"package\");\n\n    verifier.executeGoal(\"jib:dockerBuild\");\n    verifier.verifyTextInLog(\"Docker load called. value1 value2\");\n    verifier.verifyErrorFreeLog();\n  }\n\n  @Test\n  public void testExecute_empty()\n      throws InterruptedException, IOException, VerificationException, DigestException {\n    String targetImage = \"emptyimage:maven\" + System.nanoTime();\n\n    Assert.assertEquals(\"\", buildToDockerDaemonAndRun(emptyTestProject, targetImage));\n    Assert.assertEquals(\n        \"1970-01-01T00:00:00Z\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Created}}\", targetImage).run().trim());\n  }\n\n  @Test\n  public void testExecute_defaultTarget()\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n\",\n        buildToDockerDaemonAndRun(\n            defaultTargetTestProject, \"default-target-name:default-target-version\"));\n  }\n\n  @Test\n  public void testExecute_jibSkip() throws VerificationException, IOException {\n    SkippedGoalVerifier.verifyJibSkip(emptyTestProject, BuildDockerMojo.GOAL_NAME);\n  }\n\n  @Test\n  public void testExecute_jibContainerizeSkips() throws VerificationException, IOException {\n    SkippedGoalVerifier.verifyJibContainerizeSkips(emptyTestProject, BuildDockerMojo.GOAL_NAME);\n  }\n\n  @Test\n  public void testExecute_userNumeric()\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    String targetImage = \"emptyimage:maven\" + System.nanoTime();\n    buildToDockerDaemon(emptyTestProject, targetImage, \"pom.xml\");\n    Assert.assertEquals(\n        \"12345:54321\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.User}}\", targetImage).run().trim());\n  }\n\n  @Test\n  public void testExecute_userNames()\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    String targetImage = \"brokenuserimage:maven\" + System.nanoTime();\n    buildToDockerDaemon(emptyTestProject, targetImage, \"pom-broken-user.xml\");\n    Assert.assertEquals(\n        \"myuser:mygroup\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.User}}\", targetImage).run().trim());\n  }\n\n  @Test\n  public void testExecute_noToImageAndInvalidProjectName()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    buildToDockerDaemon(simpleTestProject, \"image reference ignored\", \"pom-no-to-image.xml\");\n    Assert.assertEquals(\n        \"Hello, world. \\n1970-01-01T00:00:01Z\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", \"my-artifact-id:1\").run());\n  }\n\n  @Test\n  public void testExecute_jarContainerization()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    String targetImage = \"jarcontainerizationimage:maven\" + System.nanoTime();\n    buildToDockerDaemon(simpleTestProject, targetImage, \"pom-jar-containerization.xml\");\n    Assert.assertEquals(\n        \"Hello, world. \\nImplementation-Title: hello-world\\nImplementation-Version: 1\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", targetImage).run());\n  }\n\n  @Test\n  public void testExecute_jarContainerizationOnMissingJar() throws IOException {\n    try {\n      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n      verifier.setSystemProperty(\"_TARGET_IMAGE\", \"erroronmissingjar\");\n      verifier.setAutoclean(false);\n      verifier.addCliOption(\"--file=pom-jar-containerization.xml\");\n      verifier.executeGoals(Arrays.asList(\"clean\", \"jib:dockerBuild\"));\n      Assert.fail();\n\n    } catch (VerificationException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Obtaining project build output files failed; make sure you have packaged your \"\n                  + \"project before trying to build the image. (Did you accidentally run \\\"mvn \"\n                  + \"clean jib:build\\\" instead of \\\"mvn clean package jib:build\\\"?)\"));\n    }\n  }\n\n  @Test\n  public void testExecute_jibRequireVersion_ok() throws VerificationException, IOException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    // this plugin should match 1.0\n    verifier.setSystemProperty(\"jib.requiredVersion\", \"1.0\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    verifier.executeGoals(Arrays.asList(\"package\", \"jib:dockerBuild\"));\n    verifier.verifyErrorFreeLog();\n  }\n\n  @Test\n  public void testExecute_jibRequireVersion_fail() throws IOException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n\n    try {\n      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n      verifier.setSystemProperty(\"jib.requiredVersion\", \"[,1.0]\");\n      verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n      verifier.executeGoals(Arrays.asList(\"package\", \"jib:dockerBuild\"));\n      Assert.fail();\n    } catch (VerificationException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"but is required to be [,1.0]\"));\n    }\n  }\n\n  @Test\n  public void testCredHelperConfigurationSimple()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n    buildToDockerDaemon(simpleTestProject, targetImage, \"pom-cred-helper-1.xml\");\n    Assert.assertEquals(\n        \"Hello, world. \\n1970-01-01T00:00:01Z\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", targetImage).run());\n  }\n\n  @Test\n  public void testCredHelperConfigurationComplex()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n    buildToDockerDaemon(simpleTestProject, targetImage, \"pom-cred-helper-2.xml\");\n    Assert.assertEquals(\n        \"Hello, world. \\n1970-01-01T00:00:01Z\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", targetImage).run());\n  }\n\n  @Test\n  public void testMultiPlatform()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    String targetImage = \"multiplatformproject:maven\" + System.nanoTime();\n    buildToDockerDaemon(simpleTestProject, targetImage, \"pom-multiplatform-build.xml\");\n    Assert.assertEquals(\n        \"Hello, world. \\n1970-01-01T00:00:01Z\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", targetImage).run());\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.fail;\n\nimport com.google.cloud.tools.jib.Command;\nimport com.google.cloud.tools.jib.IntegrationTestingConfiguration;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.HttpRequestTester;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.blob.Blob;\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.image.json.ManifestTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;\nimport com.google.cloud.tools.jib.image.json.V22ManifestTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.registry.LocalRegistry;\nimport com.google.cloud.tools.jib.registry.RegistryClient;\nimport com.google.common.base.Splitter;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.DigestException;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.After;\nimport org.junit.Assume;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Integration tests for {@link BuildImageMojo}. */\npublic class BuildImageMojoIntegrationTest {\n\n  @ClassRule\n  public static final LocalRegistry localRegistry =\n      new LocalRegistry(5000, \"testuser\", \"testpassword\");\n\n  private static final String dockerHost =\n      System.getenv(\"DOCKER_IP\") != null ? System.getenv(\"DOCKER_IP\") : \"localhost\";\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule public static final TestProject emptyTestProject = new TestProject(\"empty\");\n\n  @ClassRule public static final TestProject skippedTestProject = new TestProject(\"empty\");\n\n  @ClassRule\n  public static final TestProject defaultTargetTestProject = new TestProject(\"default-target\");\n\n  @ClassRule public static final TestProject servlet25Project = new TestProject(\"war_servlet25\");\n\n  @ClassRule public static final TestProject springBootProject = new TestProject(\"spring-boot\");\n\n  private static String getTestImageReference(String label) {\n    String nameBase = IntegrationTestingConfiguration.getTestRepositoryLocation() + '/';\n    return nameBase + label + System.nanoTime();\n  }\n\n  static String readDigestFile(Path digestPath) throws IOException, DigestException {\n    assertThat(Files.exists(digestPath)).isTrue();\n    String digest = new String(Files.readAllBytes(digestPath), StandardCharsets.UTF_8);\n    return DescriptorDigest.fromDigest(digest).toString();\n  }\n\n  private static boolean isJava11RuntimeOrHigher() {\n    Iterable<String> split = Splitter.on(\".\").split(System.getProperty(\"java.version\"));\n    return Integer.valueOf(split.iterator().next()) >= 11;\n  }\n\n  private static Verifier build(\n      Path projectRoot, String imageReference, String pomXml, boolean buildTwice)\n      throws VerificationException, IOException {\n    Verifier verifier = new Verifier(projectRoot.toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", imageReference);\n    if (imageReference.startsWith(dockerHost)) {\n      verifier.setSystemProperty(\"jib.allowInsecureRegistries\", \"true\");\n    }\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-X\");\n    verifier.addCliOption(\"--file=\" + pomXml);\n    verifier.executeGoals(Arrays.asList(\"clean\", \"compile\"));\n\n    if (!buildTwice) {\n      verifier.executeGoal(\"jib:build\");\n      return verifier;\n    }\n\n    // Builds twice, and checks if the second build took less time.\n    verifier.addCliOption(\"-Djib.alwaysCacheBaseImage=true\");\n    verifier.executeGoal(\"jib:build\");\n    float timeOne = getBuildTimeFromVerifierLog(verifier);\n\n    verifier.resetStreams();\n    verifier.executeGoal(\"jib:build\");\n    float timeTwo = getBuildTimeFromVerifierLog(verifier);\n\n    // The first build should take longer than the second build.\n    assertThat(timeOne).isGreaterThan(timeTwo);\n    return verifier;\n  }\n\n  /**\n   * Builds with {@code jib:build} on a project at {@code projectRoot} pushing to {@code\n   * imageReference} and run the image after pulling it.\n   */\n  private static String buildAndRun(\n      Path projectRoot, String imageReference, String pomXml, boolean buildTwice)\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    build(projectRoot, imageReference, pomXml, buildTwice).verifyErrorFreeLog();\n\n    String output = pullAndRunBuiltImage(imageReference);\n\n    try {\n      // Test pulling/running using image digest\n      String digest = readDigestFile(projectRoot.resolve(\"target/jib-image.digest\"));\n      String imageReferenceWithDigest =\n          ImageReference.parse(imageReference).withQualifier(digest).toString();\n      assertThat(pullAndRunBuiltImage(imageReferenceWithDigest)).isEqualTo(output);\n\n      // Test running using image id\n      String id = readDigestFile(projectRoot.resolve(\"target/jib-image.id\"));\n      assertThat(id).isNotEqualTo(digest);\n      assertThat(new Command(\"docker\", \"run\", \"--rm\", id).run()).isEqualTo(output);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new AssertionError(\"error replacing tag with digest\", ex);\n    }\n\n    return output;\n  }\n\n  private static String buildAndRunFromLocalBase(\n      Path projectRoot, String targetImage, String baseImage, boolean buildTwice)\n      throws VerificationException, IOException, InterruptedException {\n    Verifier verifier = new Verifier(projectRoot.toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    verifier.setSystemProperty(\"_BASE_IMAGE\", baseImage);\n    verifier.setSystemProperty(\"jib.allowInsecureRegistries\", \"true\");\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-X\");\n    verifier.addCliOption(\"--file=pom-localbase.xml\");\n    verifier.executeGoals(Arrays.asList(\"clean\", \"compile\"));\n    if (!buildTwice) {\n      verifier.executeGoal(\"jib:build\");\n      return pullAndRunBuiltImage(targetImage);\n    }\n\n    verifier.executeGoal(\"jib:build\");\n    float timeOne = getBuildTimeFromVerifierLog(verifier);\n\n    verifier.resetStreams();\n    verifier.executeGoal(\"jib:build\");\n    float timeTwo = getBuildTimeFromVerifierLog(verifier);\n\n    // The first build should take longer than the second build.\n    assertThat(timeOne).isGreaterThan(timeTwo);\n    return pullAndRunBuiltImage(targetImage);\n  }\n\n  private static String buildAndRunAdditionalTag(\n      Path projectRoot, String imageReference, String additionalTag)\n      throws VerificationException, InvalidImageReferenceException, IOException,\n          InterruptedException, DigestException {\n    Verifier verifier = new Verifier(projectRoot.toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", imageReference);\n    verifier.setSystemProperty(\"_ADDITIONAL_TAG\", additionalTag);\n    if (imageReference.startsWith(dockerHost)) {\n      verifier.setSystemProperty(\"jib.allowInsecureRegistries\", \"true\");\n    }\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-X\");\n    verifier.executeGoals(Arrays.asList(\"clean\", \"compile\", \"jib:build\"));\n    verifier.verifyErrorFreeLog();\n\n    String additionalImageReference =\n        ImageReference.parse(imageReference).withQualifier(additionalTag).toString();\n\n    String output = pullAndRunBuiltImage(imageReference);\n    String additionalOutput = pullAndRunBuiltImage(additionalImageReference);\n    assertThat(additionalOutput).isEqualTo(output);\n\n    String digest = readDigestFile(projectRoot.resolve(\"target/jib-image.digest\"));\n    String digestImageReference =\n        ImageReference.parse(imageReference).withQualifier(digest).toString();\n    String digestOutput = pullAndRunBuiltImage(digestImageReference);\n    assertThat(digestOutput).isEqualTo(output);\n\n    assertThat(getCreationTime(imageReference)).isEqualTo(Instant.EPOCH);\n    assertThat(getCreationTime(additionalImageReference)).isEqualTo(Instant.EPOCH);\n\n    return output;\n  }\n\n  private static String buildAndRunComplex(String imageReference, String pomFile)\n      throws VerificationException, IOException, InterruptedException {\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_DOCKER_HOST\", dockerHost);\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", imageReference);\n    verifier.setSystemProperty(\"_TARGET_USERNAME\", \"testuser\");\n    verifier.setSystemProperty(\"_TARGET_PASSWORD\", \"testpassword\");\n    verifier.setSystemProperty(\"sendCredentialsOverHttp\", \"true\");\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-X\");\n    verifier.addCliOption(\"--file=\" + pomFile);\n    verifier.executeGoals(Arrays.asList(\"clean\", \"compile\", \"jib:build\"));\n    verifier.verifyErrorFreeLog();\n\n    // Verify output\n    localRegistry.pull(imageReference);\n    assertDockerInspectParameters(imageReference);\n    return new Command(\"docker\", \"run\", \"--rm\", imageReference).run();\n  }\n\n  /**\n   * Pulls a built image and attempts to run it. Also verifies the container configuration and\n   * history of the built image.\n   *\n   * @param imageReference the image reference of the built image\n   * @return the container output\n   * @throws IOException if an I/O exception occurs\n   * @throws InterruptedException if the process was interrupted\n   */\n  private static String pullAndRunBuiltImage(String imageReference)\n      throws IOException, InterruptedException {\n    new Command(\"docker\", \"pull\", imageReference).run();\n    assertDockerInspectParameters(imageReference);\n    return new Command(\"docker\", \"run\", \"--rm\", imageReference).run();\n  }\n\n  private static void assertDockerInspectParameters(String imageReference)\n      throws IOException, InterruptedException {\n    String dockerInspectExposedPorts =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.ExposedPorts}}'\", imageReference)\n            .run();\n    String dockerInspectLabels =\n        new Command(\"docker\", \"inspect\", \"-f\", \"'{{json .Config.Labels}}'\", imageReference).run();\n    String history = new Command(\"docker\", \"history\", imageReference).run();\n\n    MatcherAssert.assertThat(\n        dockerInspectExposedPorts,\n        CoreMatchers.containsString(\n            \"\\\"1000/tcp\\\":{},\\\"2000/udp\\\":{},\\\"2001/udp\\\":{},\\\"2002/udp\\\":{},\\\"2003/udp\\\":{}\"));\n    MatcherAssert.assertThat(\n        dockerInspectLabels,\n        CoreMatchers.containsString(\"\\\"key1\\\":\\\"value1\\\",\\\"key2\\\":\\\"value2\\\"\"));\n    assertThat(history).contains(\"jib-maven-plugin\");\n  }\n\n  private static float getBuildTimeFromVerifierLog(Verifier verifier) throws IOException {\n    Pattern pattern = Pattern.compile(\"Building and pushing image : (?<time>.*) ms\");\n\n    for (String line :\n        Files.readAllLines(Paths.get(verifier.getBasedir(), verifier.getLogFileName()))) {\n      Matcher matcher = pattern.matcher(line);\n      if (matcher.find()) {\n        return Float.parseFloat(matcher.group(\"time\"));\n      }\n    }\n\n    fail(\"Could not find build execution time in logs\");\n    // Should not reach here.\n    return -1;\n  }\n\n  private static Instant getCreationTime(String imageReference)\n      throws IOException, InterruptedException {\n    String inspect =\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Created}}\", imageReference).run().trim();\n    return Instant.parse(inspect);\n  }\n\n  private static String getWorkingDirectory(String imageReference)\n      throws IOException, InterruptedException {\n    return new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.WorkingDir}}\", imageReference)\n        .run()\n        .trim();\n  }\n\n  private static String getEntrypoint(String imageReference)\n      throws IOException, InterruptedException {\n    return new Command(\"docker\", \"inspect\", \"-f\", \"{{.Config.Entrypoint}}\", imageReference)\n        .run()\n        .trim();\n  }\n\n  private static int getLayerSize(String imageReference) throws IOException, InterruptedException {\n    Command command =\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{join .RootFS.Layers \\\",\\\"}}\", imageReference);\n    String layers = command.run().trim();\n    return Splitter.on(\",\").splitToList(layers).size();\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Nullable private String detachedContainerName;\n\n  @Before\n  public void setUp() throws IOException, InterruptedException {\n    // Pull distroless to local registry so we can test 'from' credentials\n    localRegistry.pullAndPushToLocal(\"gcr.io/distroless/java:latest\", \"distroless/java\");\n\n    // Make sure resource file has a consistent value at the beginning of each test\n    // (testExecute_simple and testBuild_tarBase overwrite it)\n    Files.write(\n        simpleTestProject\n            .getProjectRoot()\n            .resolve(\"src\")\n            .resolve(\"main\")\n            .resolve(\"resources\")\n            .resolve(\"world\"),\n        \"world\".getBytes(StandardCharsets.UTF_8));\n  }\n\n  @After\n  public void tearDown() throws IOException, InterruptedException {\n    if (detachedContainerName != null) {\n      new Command(\"docker\", \"stop\", detachedContainerName).run();\n    }\n  }\n\n  @Test\n  public void testExecute_simple()\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    String targetImage = getTestImageReference(\"simpleimage:maven\");\n\n    // Test empty output error\n    try {\n      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n      verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n      verifier.setAutoclean(false);\n      verifier.executeGoals(Arrays.asList(\"clean\", \"jib:build\"));\n      fail();\n\n    } catch (VerificationException ex) {\n      assertThat(ex)\n          .hasMessageThat()\n          .contains(\n              \"Obtaining project build output files failed; make sure you have compiled your \"\n                  + \"project before trying to build the image. (Did you accidentally run \\\"mvn \"\n                  + \"clean jib:build\\\" instead of \\\"mvn clean compile jib:build\\\"?)\");\n    }\n\n    Instant before = Instant.now();\n\n    // The target registry these tests push to would already have all the layers cached from before,\n    // causing this test to fail sometimes with the second build being a bit slower than the first\n    // build. This file change makes sure that a new layer is always pushed the first time to solve\n    // this issue.\n    Files.write(\n        simpleTestProject\n            .getProjectRoot()\n            .resolve(\"src\")\n            .resolve(\"main\")\n            .resolve(\"resources\")\n            .resolve(\"world\"),\n        before.toString().getBytes(StandardCharsets.UTF_8));\n\n    assertThat(buildAndRun(simpleTestProject.getProjectRoot(), targetImage, \"pom.xml\", true))\n        .isEqualTo(\n            \"Hello, \"\n                + before\n                + \". An argument.\\n1970-01-01T00:00:01Z\\nrw-r--r--\\nrw-r--r--\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\");\n\n    assertThat(getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/home\");\n    assertThat(getLayerSize(targetImage)).isEqualTo(10);\n  }\n\n  @Test\n  public void testBuild_dockerDaemonBase()\n      throws IOException, InterruptedException, VerificationException {\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/simplewithdockerdaemonbase:maven\"\n            + System.nanoTime();\n\n    assertThat(\n            buildAndRunFromLocalBase(\n                simpleTestProject.getProjectRoot(),\n                targetImage,\n                \"docker://gcr.io/distroless/java:latest\",\n                false))\n        .isEqualTo(\n            \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrw-r--r--\\nrw-r--r--\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\");\n  }\n\n  @Test\n  public void testBuild_tarBase() throws IOException, InterruptedException, VerificationException {\n    Path path = temporaryFolder.getRoot().toPath().resolve(\"docker-save-distroless\");\n    new Command(\"docker\", \"save\", \"gcr.io/distroless/java:latest\", \"-o\", path.toString()).run();\n    String targetImage =\n        IntegrationTestingConfiguration.getTestRepositoryLocation()\n            + \"/simplewithtarbase:maven\"\n            + System.nanoTime();\n\n    Instant before = Instant.now();\n\n    // The target registry these tests push to would already have all the layers cached from before,\n    // causing this test to fail sometimes with the second build being a bit slower than the first\n    // build. This file change makes sure that a new layer is always pushed the first time to solve\n    // this issue.\n    Files.write(\n        simpleTestProject\n            .getProjectRoot()\n            .resolve(\"src\")\n            .resolve(\"main\")\n            .resolve(\"resources\")\n            .resolve(\"world\"),\n        before.toString().getBytes(StandardCharsets.UTF_8));\n\n    assertThat(\n            buildAndRunFromLocalBase(\n                simpleTestProject.getProjectRoot(), targetImage, \"tar://\" + path, true))\n        .isEqualTo(\n            \"Hello, \"\n                + before\n                + \". An argument.\\n1970-01-01T00:00:01Z\\nrw-r--r--\\nrw-r--r--\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\");\n  }\n\n  @Test\n  public void testExecute_failOffline() throws IOException {\n    String targetImage = getTestImageReference(\"simpleimageoffline:maven\");\n\n    // Test empty output error\n    try {\n      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n      verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n      verifier.setAutoclean(false);\n      verifier.addCliOption(\"--offline\");\n      verifier.executeGoals(Arrays.asList(\"clean\", \"compile\", \"jib:build\"));\n      fail();\n\n    } catch (VerificationException ex) {\n      assertThat(ex)\n          .hasMessageThat()\n          .contains(\"Cannot build to a container registry in offline mode\");\n    }\n  }\n\n  @Test\n  public void testExecute_simpleOnJava11()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    Assume.assumeTrue(isJava11RuntimeOrHigher());\n\n    String targetImage = getTestImageReference(\"simpleimage:maven\");\n    assertThat(\n            buildAndRun(simpleTestProject.getProjectRoot(), targetImage, \"pom-java11.xml\", false))\n        .isEqualTo(\"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\n\");\n  }\n\n  @Test\n  public void testExecute_simpleWithIncomptiableJava11()\n      throws DigestException, IOException, InterruptedException {\n    Assume.assumeTrue(isJava11RuntimeOrHigher());\n\n    try {\n      buildAndRun(\n          simpleTestProject.getProjectRoot(), \"willnotbuild\", \"pom-java11-incompatible.xml\", false);\n      fail();\n    } catch (VerificationException ex) {\n      assertThat(ex)\n          .hasMessageThat()\n          .contains(\n              \"Your project is using Java 11 but the base image is for Java 8, perhaps you should \"\n                  + \"configure a Java 11-compatible base image using the '<from><image>' \"\n                  + \"parameter, or set maven-compiler-plugin's '<target>' or '<release>' version \"\n                  + \"to 8 or below in your build configuration\");\n    }\n  }\n\n  @Test\n  public void testExecute_empty()\n      throws InterruptedException, IOException, VerificationException, DigestException {\n    String targetImage = getTestImageReference(\"emptyimage:maven\");\n    assertThat(buildAndRun(emptyTestProject.getProjectRoot(), targetImage, \"pom.xml\", false))\n        .isEmpty();\n    assertThat(getCreationTime(targetImage)).isEqualTo(Instant.EPOCH);\n    assertThat(getWorkingDirectory(targetImage)).isEmpty();\n  }\n\n  @Test\n  public void testExecute_multipleTags()\n      throws IOException, InterruptedException, InvalidImageReferenceException,\n          VerificationException, DigestException {\n    String targetImage = getTestImageReference(\"multitag-image:maven\");\n    assertThat(\n            buildAndRunAdditionalTag(\n                emptyTestProject.getProjectRoot(), targetImage, \"maven-2\" + System.nanoTime()))\n        .isEmpty();\n  }\n\n  @Test\n  public void testExecute_multipleExtraDirectories()\n      throws DigestException, VerificationException, IOException, InterruptedException {\n    String targetImage = getTestImageReference(\"simpleimage:maven\");\n    assertThat(\n            buildAndRun(\n                simpleTestProject.getProjectRoot(), targetImage, \"pom-extra-dirs.xml\", false))\n        .isEqualTo(\n            \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrw-r--r--\\nrw-r--r--\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\nbaz\\n1970-01-01T00:00:01Z\\n\");\n    assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual\n  }\n\n  @Test\n  public void testExecute_defaultTarget() throws IOException {\n    // Test error when 'to' is missing\n    try {\n      Verifier verifier = new Verifier(defaultTargetTestProject.getProjectRoot().toString());\n      verifier.setAutoclean(false);\n      verifier.executeGoals(Arrays.asList(\"clean\", \"jib:build\"));\n      fail();\n\n    } catch (VerificationException ex) {\n      assertThat(ex)\n          .hasMessageThat()\n          .contains(\n              \"Missing target image parameter, perhaps you should add a <to><image> configuration \"\n                  + \"parameter to your pom.xml or set the parameter via the commandline (e.g. 'mvn \"\n                  + \"compile jib:build -Dimage=<your image name>').\");\n    }\n  }\n\n  @Test\n  public void testExecute_complex()\n      throws IOException, InterruptedException, VerificationException, DigestException {\n    String targetImage = dockerHost + \":5000/compleximage:maven\" + System.nanoTime();\n    Instant before = Instant.now();\n    String output = buildAndRunComplex(targetImage, \"pom-complex.xml\");\n    assertThat(output)\n        .isEqualTo(\n            \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrwxr-xr-x\\nrwxrwxrwx\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\"\n                + \"-Xms512m\\n-Xdebug\\nenvvalue1\\nenvvalue2\\n\");\n    String digest =\n        readDigestFile(\n            simpleTestProject.getProjectRoot().resolve(\"target/different-jib-image.digest\"));\n    String id =\n        readDigestFile(simpleTestProject.getProjectRoot().resolve(\"different-jib-image.id\"));\n    assertThat(id).isNotEqualTo(digest);\n    assertThat(new Command(\"docker\", \"run\", \"--rm\", id).run()).isEqualTo(output);\n\n    assertThat(getCreationTime(targetImage)).isGreaterThan(before);\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/\");\n    assertThat(getEntrypoint(targetImage))\n        .isEqualTo(\n            \"[java -Xms512m -Xdebug -cp /other:/app/resources:/app/classes:/app/libs/* \"\n                + \"com.test.HelloWorld]\");\n  }\n\n  @Test\n  public void testExecute_timestampCustom()\n      throws IOException, InterruptedException, VerificationException {\n    String targetImage = dockerHost + \":5000/simpleimage:maven\" + System.nanoTime();\n    String pom = \"pom-timestamps-custom.xml\";\n    assertThat(buildAndRunComplex(targetImage, pom))\n        .isEqualTo(\n            \"Hello, world. \\n2019-06-17T16:30:00Z\\nrw-r--r--\\nrw-r--r--\\n\"\n                + \"foo\\ncat\\n2019-06-17T16:30:00Z\\n2019-06-17T16:30:00Z\\n\");\n\n    assertThat(getCreationTime(targetImage)).isEqualTo(Instant.parse(\"2013-11-05T06:29:30Z\"));\n  }\n\n  @Test\n  public void testExecute_complex_sameFromAndToRegistry()\n      throws IOException, InterruptedException, VerificationException {\n    String targetImage = dockerHost + \":5000/compleximage:maven\" + System.nanoTime();\n    assertThat(buildAndRunComplex(targetImage, \"pom-complex.xml\"))\n        .isEqualTo(\n            \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrwxr-xr-x\\nrwxrwxrwx\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\"\n                + \"-Xms512m\\n-Xdebug\\nenvvalue1\\nenvvalue2\\n\");\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/\");\n  }\n\n  @Test\n  public void testExecute_complexProperties()\n      throws InterruptedException, VerificationException, IOException {\n    String targetImage = dockerHost + \":5000/compleximage:maven\" + System.nanoTime();\n    assertThat(buildAndRunComplex(targetImage, \"pom-complex-properties.xml\"))\n        .isEqualTo(\n            \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrwxr-xr-x\\nrwxrwxrwx\\nfoo\\ncat\\n\"\n                + \"1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\"\n                + \"-Xms512m\\n-Xdebug\\nenvvalue1\\nenvvalue2\\n\");\n    assertThat(getWorkingDirectory(targetImage)).isEqualTo(\"/\");\n  }\n\n  @Test\n  public void testExecute_jibSkip() throws VerificationException, IOException {\n    SkippedGoalVerifier.verifyJibSkip(skippedTestProject, BuildImageMojo.GOAL_NAME);\n  }\n\n  @Test\n  public void testExecute_jibContainerizeSkips() throws VerificationException, IOException {\n    SkippedGoalVerifier.verifyJibContainerizeSkips(simpleTestProject, BuildDockerMojo.GOAL_NAME);\n  }\n\n  @Test\n  public void testExecute_jibRequireVersion_ok() throws VerificationException, IOException {\n    String targetImage = dockerHost + \":5000/simpleimage:maven\" + System.nanoTime();\n\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    // properties required to push to :5000 for plain pom.xml\n    verifier.setSystemProperty(\"jib.to.auth.username\", \"testuser\");\n    verifier.setSystemProperty(\"jib.to.auth.password\", \"testpassword\");\n    verifier.setSystemProperty(\"sendCredentialsOverHttp\", \"true\");\n    verifier.setSystemProperty(\"jib.allowInsecureRegistries\", \"true\");\n    // this test plugin should match 1.0\n    verifier.setSystemProperty(\"jib.requiredVersion\", \"1.0\");\n    verifier.executeGoals(Arrays.asList(\"package\", \"jib:build\"));\n    verifier.verifyErrorFreeLog();\n  }\n\n  @Test\n  public void testExecute_jibRequireVersion_fail() throws IOException {\n    try {\n      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n      // other properties aren't required as this should fail due to jib.requiredVersion\n      verifier.setSystemProperty(\"_TARGET_IMAGE\", \"ignored\");\n      // this plugin should be > 1.0 and so jib:build should fail\n      verifier.setSystemProperty(\"jib.requiredVersion\", \"[,1.0]\");\n      verifier.executeGoals(Arrays.asList(\"package\", \"jib:build\"));\n      fail();\n    } catch (VerificationException ex) {\n      assertThat(ex).hasMessageThat().contains(\"but is required to be [,1.0]\");\n    }\n  }\n\n  @Test\n  public void testExecute_jettyServlet25()\n      throws VerificationException, IOException, InterruptedException {\n    buildAndRunWebApp(servlet25Project, \"jetty-servlet25:maven\", \"pom.xml\");\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080/hello\"));\n  }\n\n  @Test\n  public void testExecute_tomcatServlet25()\n      throws VerificationException, IOException, InterruptedException {\n    buildAndRunWebApp(servlet25Project, \"tomcat-servlet25:maven\", \"pom-tomcat.xml\");\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080/hello\"));\n  }\n\n  @Test\n  public void testExecute_springBootPackaged()\n      throws VerificationException, IOException, InterruptedException {\n    buildAndRunWebApp(springBootProject, \"spring-boot:maven\", \"pom.xml\");\n\n    String sizeOutput =\n        new Command(\n                \"docker\",\n                \"exec\",\n                detachedContainerName,\n                \"/busybox/wc\",\n                \"-c\",\n                \"/app/classpath/spring-boot-0.1.0.original.jar\")\n            .run();\n    assertThat(sizeOutput).contains(\" /app/classpath/spring-boot-0.1.0.original.jar\");\n    int fileSize = Integer.parseInt(sizeOutput.substring(0, sizeOutput.indexOf(' ')));\n    assertThat(fileSize).isLessThan(3000); // should not be a large fat jar\n\n    HttpRequestTester.verifyBody(\n        \"Hello world\",\n        new URL(\"http://\" + HttpRequestTester.fetchDockerHostForHttpRequest() + \":8080\"));\n  }\n\n  @Test\n  public void testExecute_multiPlatformBuild()\n      throws IOException, VerificationException, RegistryException {\n    String targetImage = dockerHost + \":5000/multiplatform:maven\" + System.nanoTime();\n\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n\n    verifier.setSystemProperty(\"jib.to.auth.username\", \"testuser\");\n    verifier.setSystemProperty(\"jib.to.auth.password\", \"testpassword\");\n    verifier.setSystemProperty(\"sendCredentialsOverHttp\", \"true\");\n    verifier.setSystemProperty(\"jib.allowInsecureRegistries\", \"true\");\n\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"--file=pom-multiplatform-build.xml\");\n    verifier.executeGoals(Arrays.asList(\"clean\", \"compile\", \"jib:build\"));\n    verifier.verifyErrorFreeLog();\n\n    FailoverHttpClient httpClient = new FailoverHttpClient(true, true, ignored -> {});\n    RegistryClient registryClient =\n        RegistryClient.factory(\n                EventHandlers.NONE, dockerHost + \":5000\", \"multiplatform\", httpClient)\n            .setCredential(Credential.from(\"testuser\", \"testpassword\"))\n            .newRegistryClient();\n    registryClient.configureBasicAuth();\n\n    // manifest list by tag \":latest\"\n    ManifestTemplate manifestList = registryClient.pullManifest(\"latest\").getManifest();\n    assertThat(manifestList).isInstanceOf(V22ManifestListTemplate.class);\n    V22ManifestListTemplate v22ManifestList = (V22ManifestListTemplate) manifestList;\n\n    assertThat(v22ManifestList.getManifests().size()).isEqualTo(2);\n    ManifestDescriptorTemplate.Platform platform1 =\n        v22ManifestList.getManifests().get(0).getPlatform();\n    ManifestDescriptorTemplate.Platform platform2 =\n        v22ManifestList.getManifests().get(1).getPlatform();\n\n    assertThat(platform1.getArchitecture()).isEqualTo(\"arm64\");\n    assertThat(platform1.getOs()).isEqualTo(\"linux\");\n    assertThat(platform2.getArchitecture()).isEqualTo(\"amd64\");\n    assertThat(platform2.getOs()).isEqualTo(\"linux\");\n\n    // manifest list by tag \":another\"\n    ManifestTemplate anotherManifestList = registryClient.pullManifest(\"another\").getManifest();\n    assertThat(JsonTemplateMapper.toUtf8String(anotherManifestList))\n        .isEqualTo(JsonTemplateMapper.toUtf8String(manifestList));\n\n    // Check arm64/linux container config.\n    List<String> arm64Digests = v22ManifestList.getDigestsForPlatform(\"arm64\", \"linux\");\n    assertThat(arm64Digests.size()).isEqualTo(1);\n    String arm64Digest = arm64Digests.get(0);\n\n    ManifestTemplate arm64Manifest = registryClient.pullManifest(arm64Digest).getManifest();\n    assertThat(arm64Manifest).isInstanceOf(V22ManifestTemplate.class);\n    V22ManifestTemplate arm64V22Manifest = (V22ManifestTemplate) arm64Manifest;\n    DescriptorDigest arm64ConfigDigest = arm64V22Manifest.getContainerConfiguration().getDigest();\n\n    Blob arm64ConfigBlob = registryClient.pullBlob(arm64ConfigDigest, ignored -> {}, ignored -> {});\n    String arm64Config = Blobs.writeToString(arm64ConfigBlob);\n    assertThat(arm64Config).contains(\"\\\"architecture\\\":\\\"arm64\\\"\");\n    assertThat(arm64Config).contains(\"\\\"os\\\":\\\"linux\\\"\");\n\n    // Check amd64/linux container config.\n    List<String> amd64Digests = v22ManifestList.getDigestsForPlatform(\"amd64\", \"linux\");\n    assertThat(amd64Digests.size()).isEqualTo(1);\n    String amd64Digest = amd64Digests.get(0);\n\n    ManifestTemplate amd64Manifest = registryClient.pullManifest(amd64Digest).getManifest();\n    assertThat(amd64Manifest).isInstanceOf(V22ManifestTemplate.class);\n    V22ManifestTemplate amd64V22Manifest = (V22ManifestTemplate) amd64Manifest;\n    DescriptorDigest amd64ConfigDigest = amd64V22Manifest.getContainerConfiguration().getDigest();\n\n    Blob amd64ConfigBlob = registryClient.pullBlob(amd64ConfigDigest, ignored -> {}, ignored -> {});\n    String amd64Config = Blobs.writeToString(amd64ConfigBlob);\n    assertThat(amd64Config).contains(\"\\\"architecture\\\":\\\"amd64\\\"\");\n    assertThat(amd64Config).contains(\"\\\"os\\\":\\\"linux\\\"\");\n  }\n\n  private void buildAndRunWebApp(TestProject project, String label, String pomXml)\n      throws VerificationException, IOException, InterruptedException {\n    String targetImage = getTestImageReference(label);\n\n    Verifier verifier = new Verifier(project.getProjectRoot().toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    if (targetImage.startsWith(dockerHost)) {\n      verifier.setSystemProperty(\"jib.allowInsecureRegistries\", \"true\");\n    }\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-X\");\n    verifier.addCliOption(\"--file=\" + pomXml);\n    verifier.executeGoals(Arrays.asList(\"clean\", \"package\", \"jib:build\"));\n    verifier.verifyErrorFreeLog();\n\n    detachedContainerName =\n        new Command(\"docker\", \"run\", \"--rm\", \"--detach\", \"-p8080:8080\", targetImage).run().trim();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildTarMojoIntegrationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.Command;\nimport java.io.IOException;\nimport java.security.DigestException;\nimport java.util.Arrays;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\npublic class BuildTarMojoIntegrationTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule public static final TestProject skippedTestProject = new TestProject(\"empty\");\n\n  @Test\n  public void testExecute_simple()\n      throws VerificationException, IOException, InterruptedException, DigestException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(\"jib.useOnlyProjectCache\", \"true\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    verifier.setAutoclean(false);\n    verifier.executeGoal(\"package\");\n\n    verifier.executeGoal(\"jib:\" + BuildTarMojo.GOAL_NAME);\n    verifier.verifyErrorFreeLog();\n\n    BuildImageMojoIntegrationTest.readDigestFile(\n        simpleTestProject.getProjectRoot().resolve(\"target/jib-image.digest\"));\n\n    new Command(\n            \"docker\",\n            \"load\",\n            \"--input\",\n            simpleTestProject\n                .getProjectRoot()\n                .resolve(\"target\")\n                .resolve(\"different-jib-image.tar\")\n                .toString())\n        .run();\n    Assert.assertEquals(\n        \"Hello, world. An argument.\\n1970-01-01T00:00:01Z\\nrw-r--r--\\nrw-r--r--\\nfoo\\ncat\\n1970-01-01T00:00:01Z\\n1970-01-01T00:00:01Z\\n\",\n        new Command(\"docker\", \"run\", \"--rm\", targetImage).run());\n    Assert.assertEquals(\n        \"1970-01-01T00:00:00Z\",\n        new Command(\"docker\", \"inspect\", \"-f\", \"{{.Created}}\", targetImage).run().trim());\n  }\n\n  @Test\n  public void testExecute_jibSkip() throws VerificationException, IOException {\n    SkippedGoalVerifier.verifyJibSkip(skippedTestProject, BuildTarMojo.GOAL_NAME);\n  }\n\n  @Test\n  public void testExecute_jibContainerizeSkips() throws VerificationException, IOException {\n    SkippedGoalVerifier.verifyJibContainerizeSkips(simpleTestProject, BuildDockerMojo.GOAL_NAME);\n  }\n\n  @Test\n  public void testExecute_jibRequireVersion_ok() throws VerificationException, IOException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    // this plugin should match 1.0\n    verifier.setSystemProperty(\"jib.requiredVersion\", \"1.0\");\n    verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n    verifier.executeGoals(Arrays.asList(\"package\", \"jib:buildTar\"));\n    verifier.verifyErrorFreeLog();\n  }\n\n  @Test\n  public void testExecute_jibRequireVersion_fail() throws IOException {\n    String targetImage = \"simpleimage:maven\" + System.nanoTime();\n\n    try {\n      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n      verifier.setSystemProperty(\"jib.requiredVersion\", \"[,1.0]\");\n      verifier.setSystemProperty(\"_TARGET_IMAGE\", targetImage);\n      verifier.executeGoals(Arrays.asList(\"package\", \"jib:buildTar\"));\n      Assert.fail();\n    } catch (VerificationException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"but is required to be [,1.0]\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.docker.CliDockerClient;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException;\nimport com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException;\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\nimport com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidAppRootException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidCreationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidPlatformException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException;\nimport com.google.cloud.tools.jib.plugins.common.MainClassInferenceException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.InvalidGlobalConfigException;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.MojoFailureException;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\n\n/** Builds a container image and exports to the default Docker daemon. */\n@Mojo(\n    name = BuildDockerMojo.GOAL_NAME,\n    requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM,\n    threadSafe = true)\npublic class BuildDockerMojo extends JibPluginConfiguration {\n\n  @VisibleForTesting static final String GOAL_NAME = \"dockerBuild\";\n  private static final String HELPFUL_SUGGESTIONS_PREFIX = \"Build to Docker daemon failed\";\n\n  @Override\n  public void execute() throws MojoExecutionException, MojoFailureException {\n    checkJibVersion();\n    if (MojoCommon.shouldSkipJibExecution(this)) {\n      return;\n    }\n\n    Path dockerExecutable = getDockerClientExecutable();\n    boolean isDockerInstalled =\n        dockerExecutable == null\n            ? CliDockerClient.isDefaultDockerInstalled()\n            : CliDockerClient.isDockerInstalled(dockerExecutable);\n    if (!isDockerInstalled) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forDockerNotInstalled(HELPFUL_SUGGESTIONS_PREFIX));\n    }\n\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        getSession().getSettings(), getSettingsDecrypter());\n\n    TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n    MavenProjectProperties projectProperties =\n        MavenProjectProperties.getForProject(\n            Preconditions.checkNotNull(descriptor),\n            getProject(),\n            getSession(),\n            getLog(),\n            tempDirectoryProvider,\n            getInjectedPluginExtensions());\n\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());\n    try {\n      GlobalConfig globalConfig = GlobalConfig.readConfig();\n      updateCheckFuture = MojoCommon.newUpdateChecker(projectProperties, globalConfig, getLog());\n\n      PluginConfigurationProcessor.createJibBuildRunnerForDockerDaemonImage(\n              new MavenRawConfiguration(this),\n              new MavenSettingsServerCredentials(\n                  getSession().getSettings(), getSettingsDecrypter()),\n              projectProperties,\n              globalConfig,\n              new MavenHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))\n          .runBuild();\n\n    } catch (InvalidAppRootException ex) {\n      throw new MojoExecutionException(\n          \"<container><appRoot> is not an absolute Unix-style path: \" + ex.getInvalidPathValue(),\n          ex);\n\n    } catch (InvalidContainerizingModeException ex) {\n      throw new MojoExecutionException(\n          \"invalid value for <containerizingMode>: \" + ex.getInvalidContainerizingMode(), ex);\n\n    } catch (InvalidWorkingDirectoryException ex) {\n      throw new MojoExecutionException(\n          \"<container><workingDirectory> is not an absolute Unix-style path: \"\n              + ex.getInvalidPathValue(),\n          ex);\n    } catch (InvalidPlatformException ex) {\n      throw new MojoExecutionException(\n          \"<from><platforms> contains a platform configuration that is missing required values or has invalid values: \"\n              + ex.getMessage()\n              + \": \"\n              + ex.getInvalidPlatform(),\n          ex);\n    } catch (InvalidContainerVolumeException ex) {\n      throw new MojoExecutionException(\n          \"<container><volumes> is not an absolute Unix-style path: \" + ex.getInvalidVolume(), ex);\n\n    } catch (InvalidFilesModificationTimeException ex) {\n      throw new MojoExecutionException(\n          \"<container><filesModificationTime> should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or special keyword \\\"EPOCH_PLUS_SECOND\\\": \"\n              + ex.getInvalidFilesModificationTime(),\n          ex);\n\n    } catch (InvalidCreationTimeException ex) {\n      throw new MojoExecutionException(\n          \"<container><creationTime> should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or a special keyword (\\\"EPOCH\\\", \"\n              + \"\\\"USE_CURRENT_TIMESTAMP\\\"): \"\n              + ex.getInvalidCreationTime(),\n          ex);\n\n    } catch (JibPluginExtensionException ex) {\n      String extensionName = ex.getExtensionClass().getName();\n      throw new MojoExecutionException(\n          \"error running extension '\" + extensionName + \"': \" + ex.getMessage(), ex);\n\n    } catch (IncompatibleBaseImageJavaVersionException ex) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForMaven(\n              ex.getBaseImageMajorJavaVersion(), ex.getProjectMajorJavaVersion()),\n          ex);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex);\n\n    } catch (IOException\n        | CacheDirectoryCreationException\n        | MainClassInferenceException\n        | InvalidGlobalConfigException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex);\n\n    } catch (BuildStepsExecutionException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex.getCause());\n\n    } catch (ExtraDirectoryNotFoundException ex) {\n      throw new MojoExecutionException(\n          \"<extraDirectories><paths> contain \\\"from\\\" directory that doesn't exist locally: \"\n              + ex.getPath(),\n          ex);\n    } finally {\n      tempDirectoryProvider.close();\n      MojoCommon.finishUpdateChecker(projectProperties, updateCheckFuture);\n      projectProperties.waitForLoggingThread();\n      getLog().info(\"\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException;\nimport com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException;\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\nimport com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidAppRootException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidCreationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidPlatformException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException;\nimport com.google.cloud.tools.jib.plugins.common.MainClassInferenceException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.InvalidGlobalConfigException;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Strings;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.MojoFailureException;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\n\n/** Builds a container image. */\n@Mojo(\n    name = BuildImageMojo.GOAL_NAME,\n    requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM,\n    threadSafe = true)\npublic class BuildImageMojo extends JibPluginConfiguration {\n\n  @VisibleForTesting static final String GOAL_NAME = \"build\";\n\n  private static final String HELPFUL_SUGGESTIONS_PREFIX = \"Build image failed\";\n\n  @Override\n  public void execute() throws MojoExecutionException, MojoFailureException {\n    checkJibVersion();\n    if (MojoCommon.shouldSkipJibExecution(this)) {\n      return;\n    }\n\n    // Validates 'format'.\n    if (Arrays.stream(ImageFormat.values()).noneMatch(value -> value.name().equals(getFormat()))) {\n      throw new MojoFailureException(\n          \"<format> parameter is configured with value '\"\n              + getFormat()\n              + \"', but the only valid configuration options are '\"\n              + ImageFormat.Docker\n              + \"' and '\"\n              + ImageFormat.OCI\n              + \"'.\");\n    }\n\n    // Parses 'to' into image reference.\n    if (Strings.isNullOrEmpty(getTargetImage())) {\n      throw new MojoFailureException(\n          HelpfulSuggestions.forToNotConfigured(\n              \"Missing target image parameter\",\n              \"<to><image>\",\n              \"pom.xml\",\n              \"mvn compile jib:build -Dimage=<your image name>\"));\n    }\n\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        getSession().getSettings(), getSettingsDecrypter());\n\n    TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n    MavenProjectProperties projectProperties =\n        MavenProjectProperties.getForProject(\n            Preconditions.checkNotNull(descriptor),\n            getProject(),\n            getSession(),\n            getLog(),\n            tempDirectoryProvider,\n            getInjectedPluginExtensions());\n\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());\n    try {\n      GlobalConfig globalConfig = GlobalConfig.readConfig();\n      updateCheckFuture = MojoCommon.newUpdateChecker(projectProperties, globalConfig, getLog());\n\n      PluginConfigurationProcessor.createJibBuildRunnerForRegistryImage(\n              new MavenRawConfiguration(this),\n              new MavenSettingsServerCredentials(\n                  getSession().getSettings(), getSettingsDecrypter()),\n              projectProperties,\n              globalConfig,\n              new MavenHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))\n          .runBuild();\n\n    } catch (InvalidAppRootException ex) {\n      throw new MojoExecutionException(\n          \"<container><appRoot> is not an absolute Unix-style path: \" + ex.getInvalidPathValue(),\n          ex);\n\n    } catch (InvalidContainerizingModeException ex) {\n      throw new MojoExecutionException(\n          \"invalid value for <containerizingMode>: \" + ex.getInvalidContainerizingMode(), ex);\n\n    } catch (InvalidWorkingDirectoryException ex) {\n      throw new MojoExecutionException(\n          \"<container><workingDirectory> is not an absolute Unix-style path: \"\n              + ex.getInvalidPathValue(),\n          ex);\n    } catch (InvalidPlatformException ex) {\n      throw new MojoExecutionException(\n          \"<from><platforms> contains a platform configuration that is missing required values or has invalid values: \"\n              + ex.getMessage()\n              + \": \"\n              + ex.getInvalidPlatform(),\n          ex);\n    } catch (InvalidContainerVolumeException ex) {\n      throw new MojoExecutionException(\n          \"<container><volumes> is not an absolute Unix-style path: \" + ex.getInvalidVolume(), ex);\n\n    } catch (InvalidFilesModificationTimeException ex) {\n      throw new MojoExecutionException(\n          \"<container><filesModificationTime> should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or special keyword \\\"EPOCH_PLUS_SECOND\\\": \"\n              + ex.getInvalidFilesModificationTime(),\n          ex);\n\n    } catch (InvalidCreationTimeException ex) {\n      throw new MojoExecutionException(\n          \"<container><creationTime> should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or a special keyword (\\\"EPOCH\\\", \"\n              + \"\\\"USE_CURRENT_TIMESTAMP\\\"): \"\n              + ex.getInvalidCreationTime(),\n          ex);\n\n    } catch (JibPluginExtensionException ex) {\n      String extensionName = ex.getExtensionClass().getName();\n      throw new MojoExecutionException(\n          \"error running extension '\" + extensionName + \"': \" + ex.getMessage(), ex);\n\n    } catch (IncompatibleBaseImageJavaVersionException ex) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForMaven(\n              ex.getBaseImageMajorJavaVersion(), ex.getProjectMajorJavaVersion()),\n          ex);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex);\n\n    } catch (IOException\n        | CacheDirectoryCreationException\n        | MainClassInferenceException\n        | InvalidGlobalConfigException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex);\n\n    } catch (BuildStepsExecutionException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex.getCause());\n\n    } catch (ExtraDirectoryNotFoundException ex) {\n      throw new MojoExecutionException(\n          \"<extraDirectories><paths> contain \\\"from\\\" directory that doesn't exist locally: \"\n              + ex.getPath(),\n          ex);\n    } finally {\n      tempDirectoryProvider.close();\n      MojoCommon.finishUpdateChecker(projectProperties, updateCheckFuture);\n      projectProperties.waitForLoggingThread();\n      getLog().info(\"\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException;\nimport com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException;\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\nimport com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidAppRootException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerVolumeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidCreationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidFilesModificationTimeException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidPlatformException;\nimport com.google.cloud.tools.jib.plugins.common.InvalidWorkingDirectoryException;\nimport com.google.cloud.tools.jib.plugins.common.MainClassInferenceException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.InvalidGlobalConfigException;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.MojoFailureException;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\n\n/**\n * Builds a container image and exports to disk at the configured location ({@code\n * ${project.build.directory}/jib-image.tar} by default).\n */\n@Mojo(\n    name = BuildTarMojo.GOAL_NAME,\n    requiresDependencyResolution = ResolutionScope.RUNTIME_PLUS_SYSTEM,\n    threadSafe = true)\npublic class BuildTarMojo extends JibPluginConfiguration {\n\n  @VisibleForTesting static final String GOAL_NAME = \"buildTar\";\n\n  private static final String HELPFUL_SUGGESTIONS_PREFIX = \"Building image tarball failed\";\n\n  @Override\n  public void execute() throws MojoExecutionException, MojoFailureException {\n    checkJibVersion();\n    if (MojoCommon.shouldSkipJibExecution(this)) {\n      return;\n    }\n\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        getSession().getSettings(), getSettingsDecrypter());\n\n    TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider();\n    MavenProjectProperties projectProperties =\n        MavenProjectProperties.getForProject(\n            Preconditions.checkNotNull(descriptor),\n            getProject(),\n            getSession(),\n            getLog(),\n            tempDirectoryProvider,\n            getInjectedPluginExtensions());\n\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());\n    try {\n      GlobalConfig globalConfig = GlobalConfig.readConfig();\n      updateCheckFuture = MojoCommon.newUpdateChecker(projectProperties, globalConfig, getLog());\n\n      PluginConfigurationProcessor.createJibBuildRunnerForTarImage(\n              new MavenRawConfiguration(this),\n              new MavenSettingsServerCredentials(\n                  getSession().getSettings(), getSettingsDecrypter()),\n              projectProperties,\n              globalConfig,\n              new MavenHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))\n          .runBuild();\n\n    } catch (InvalidAppRootException ex) {\n      throw new MojoExecutionException(\n          \"<container><appRoot> is not an absolute Unix-style path: \" + ex.getInvalidPathValue(),\n          ex);\n\n    } catch (InvalidContainerizingModeException ex) {\n      throw new MojoExecutionException(\n          \"invalid value for <containerizingMode>: \" + ex.getInvalidContainerizingMode(), ex);\n\n    } catch (InvalidWorkingDirectoryException ex) {\n      throw new MojoExecutionException(\n          \"<container><workingDirectory> is not an absolute Unix-style path: \"\n              + ex.getInvalidPathValue(),\n          ex);\n    } catch (InvalidPlatformException ex) {\n      throw new MojoExecutionException(\n          \"<from><platforms> contains a platform configuration that is missing required values or has invalid values: \"\n              + ex.getMessage()\n              + \": \"\n              + ex.getInvalidPlatform(),\n          ex);\n    } catch (InvalidContainerVolumeException ex) {\n      throw new MojoExecutionException(\n          \"<container><volumes> is not an absolute Unix-style path: \" + ex.getInvalidVolume(), ex);\n\n    } catch (InvalidFilesModificationTimeException ex) {\n      throw new MojoExecutionException(\n          \"<container><filesModificationTime> should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or special keyword \\\"EPOCH_PLUS_SECOND\\\": \"\n              + ex.getInvalidFilesModificationTime(),\n          ex);\n\n    } catch (InvalidCreationTimeException ex) {\n      throw new MojoExecutionException(\n          \"<container><creationTime> should be an ISO 8601 date-time (see \"\n              + \"DateTimeFormatter.ISO_DATE_TIME) or a special keyword (\\\"EPOCH\\\", \"\n              + \"\\\"USE_CURRENT_TIMESTAMP\\\"): \"\n              + ex.getInvalidCreationTime(),\n          ex);\n\n    } catch (JibPluginExtensionException ex) {\n      String extensionName = ex.getExtensionClass().getName();\n      throw new MojoExecutionException(\n          \"error running extension '\" + extensionName + \"': \" + ex.getMessage(), ex);\n\n    } catch (IncompatibleBaseImageJavaVersionException ex) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForMaven(\n              ex.getBaseImageMajorJavaVersion(), ex.getProjectMajorJavaVersion()),\n          ex);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new MojoExecutionException(\n          HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex);\n\n    } catch (IOException\n        | CacheDirectoryCreationException\n        | MainClassInferenceException\n        | InvalidGlobalConfigException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex);\n\n    } catch (BuildStepsExecutionException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex.getCause());\n\n    } catch (ExtraDirectoryNotFoundException ex) {\n      throw new MojoExecutionException(\n          \"<extraDirectories><paths> contain \\\"from\\\" directory that doesn't exist locally: \"\n              + ex.getPath(),\n          ex);\n    } finally {\n      tempDirectoryProvider.close();\n      MojoCommon.finishUpdateChecker(projectProperties, updateCheckFuture);\n      projectProperties.waitForLoggingThread();\n      getLog().info(\"\");\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension;\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Strings;\nimport java.io.File;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport javax.inject.Inject;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.plugin.AbstractMojo;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.descriptor.PluginDescriptor;\nimport org.apache.maven.plugins.annotations.Component;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.Parameter;\nimport org.apache.maven.project.MavenProject;\nimport org.apache.maven.settings.crypto.SettingsDecrypter;\n\n/** Defines the configuration parameters for Jib. Jib {@link Mojo}s should extend this class. */\npublic abstract class JibPluginConfiguration extends AbstractMojo {\n\n  /** Base for {@link FromAuthConfiguration} and {@link ToAuthConfiguration}. */\n  private static class AuthConfiguration implements AuthProperty {\n\n    @Nullable @Parameter private String username;\n    @Nullable @Parameter private String password;\n    private final String source;\n\n    private AuthConfiguration(String source) {\n      this.source = source;\n    }\n\n    @Override\n    @Nullable\n    public String getUsername() {\n      return username;\n    }\n\n    @Override\n    @Nullable\n    public String getPassword() {\n      return password;\n    }\n\n    @Override\n    public String getAuthDescriptor() {\n      return \"<\" + source + \"><auth>\";\n    }\n\n    @Override\n    public String getUsernameDescriptor() {\n      return getAuthDescriptor() + \"<username>\";\n    }\n\n    @Override\n    public String getPasswordDescriptor() {\n      return getAuthDescriptor() + \"<password>\";\n    }\n  }\n\n  /** Used to configure {@code from.auth} parameters. */\n  public static class FromAuthConfiguration extends AuthConfiguration {\n\n    public FromAuthConfiguration() {\n      super(\"from\");\n    }\n  }\n\n  /** Used to configure {@code to.auth} parameters. */\n  public static class ToAuthConfiguration extends AuthConfiguration {\n\n    public ToAuthConfiguration() {\n      super(\"to\");\n    }\n  }\n\n  /** Used to configure {@code extraDirectories.permissions} parameter. */\n  public static class PermissionConfiguration {\n\n    @Nullable @Parameter private String file;\n    @Nullable @Parameter private String mode;\n\n    // Need default constructor for Maven\n    public PermissionConfiguration() {}\n\n    @VisibleForTesting\n    PermissionConfiguration(String file, String mode) {\n      this.file = file;\n      this.mode = mode;\n    }\n\n    Optional<String> getFile() {\n      return Optional.ofNullable(file);\n    }\n\n    Optional<String> getMode() {\n      return Optional.ofNullable(mode);\n    }\n  }\n\n  /** Configuration for {@code platform} parameter. */\n  public static class PlatformParameters implements PlatformConfiguration {\n\n    private static PlatformParameters of(String osArchitecture) {\n      Matcher matcher = Pattern.compile(\"([^/ ]+)/([^/ ]+)\").matcher(osArchitecture);\n      if (!matcher.matches()) {\n        throw new IllegalArgumentException(\"Platform must be of form os/architecture.\");\n      }\n      PlatformParameters platformParameters = new PlatformParameters();\n      platformParameters.os = matcher.group(1);\n      platformParameters.architecture = matcher.group(2);\n      return platformParameters;\n    }\n\n    @Nullable @Parameter private String os;\n    @Nullable @Parameter private String architecture;\n\n    @Override\n    public Optional<String> getOsName() {\n      return Optional.ofNullable(os);\n    }\n\n    @Override\n    public Optional<String> getArchitectureName() {\n      return Optional.ofNullable(architecture);\n    }\n  }\n\n  /** Configuration for {@code [from|to].credHelper} parameter. */\n  public static class CredHelperParameters implements CredHelperConfiguration {\n    @Nullable @Parameter private String helper;\n    @Parameter private Map<String, String> environment = new HashMap<>();\n\n    @Override\n    public Optional<String> getHelperName() {\n      return Optional.ofNullable(helper);\n    }\n\n    @Override\n    public Map<String, String> getEnvironment() {\n      return environment;\n    }\n\n    public void setHelper(@Nullable String helper) {\n      this.helper = helper;\n    }\n\n    /**\n     * Default setter for Maven. Makes this syntax possible:\n     *\n     * <pre>{@code\n     * <configuration>\n     *   <to>\n     *     ...\n     *     <credHelper>ecr-login</credHelper>\n     *     ...\n     *   </to>\n     * </configuration>\n     * }</pre>\n     *\n     * @param helper the credential helper\n     */\n    public void set(@Nullable String helper) {\n      this.helper = helper;\n    }\n  }\n\n  /** Configuration for {@code from} parameter. */\n  public static class FromConfiguration {\n\n    @Nullable @Parameter private String image;\n    @Parameter private CredHelperParameters credHelper = new CredHelperParameters();\n    @Parameter private FromAuthConfiguration auth = new FromAuthConfiguration();\n    @Parameter private List<PlatformParameters> platforms;\n\n    /** Constructor for defaults. */\n    public FromConfiguration() {\n      platforms = Collections.singletonList(PlatformParameters.of(\"linux/amd64\"));\n    }\n  }\n\n  /** Configuration for {@code to} parameter, where image is required. */\n  public static class ToConfiguration {\n\n    @Nullable @Parameter private String image;\n    @Parameter private List<String> tags = Collections.emptyList();\n    @Parameter private CredHelperParameters credHelper = new CredHelperParameters();\n    @Parameter private ToAuthConfiguration auth = new ToAuthConfiguration();\n\n    public void set(String image) {\n      this.image = image;\n    }\n  }\n\n  /** Configuration for {@code container} parameter. */\n  public static class ContainerParameters {\n\n    // Note: `entrypoint` and `args` are @Nullable to handle inheriting values from the base image\n\n    @Nullable @Parameter private List<String> entrypoint;\n    @Parameter private List<String> jvmFlags = Collections.emptyList();\n    @Parameter private Map<String, String> environment = Collections.emptyMap();\n    @Parameter private List<String> extraClasspath = Collections.emptyList();\n    private boolean expandClasspathDependencies;\n    @Nullable @Parameter private String mainClass;\n    @Nullable @Parameter private List<String> args;\n    @Parameter private String format = \"Docker\";\n    @Parameter private List<String> ports = Collections.emptyList();\n    @Parameter private List<String> volumes = Collections.emptyList();\n    @Parameter private Map<String, String> labels = Collections.emptyMap();\n    @Parameter private String appRoot = \"\";\n    @Nullable @Parameter private String user;\n    @Nullable @Parameter private String workingDirectory;\n    @Parameter private String filesModificationTime = \"EPOCH_PLUS_SECOND\";\n    @Parameter private String creationTime = \"EPOCH\";\n  }\n\n  /** Configuration for the {@code extraDirectories} parameter. */\n  public static class ExtraDirectoriesParameters {\n\n    @Parameter private List<ExtraDirectoryParameters> paths = Collections.emptyList();\n    @Parameter private List<PermissionConfiguration> permissions = Collections.emptyList();\n\n    public List<ExtraDirectoryParameters> getPaths() {\n      return paths;\n    }\n  }\n\n  /** A bean that configures the source and destination of an extra directory. */\n  public static class ExtraDirectoryParameters implements ExtraDirectoriesConfiguration {\n\n    @Parameter private File from = new File(\"\");\n    @Parameter private String into = \"/\";\n    @Parameter private List<String> includes = Collections.emptyList();\n    @Parameter private List<String> excludes = Collections.emptyList();\n\n    // Need default constructor for Maven\n    public ExtraDirectoryParameters() {}\n\n    ExtraDirectoryParameters(File from, String into) {\n      this.from = from;\n      this.into = into;\n    }\n\n    // Allows <path>source</path> shorthand instead of forcing\n    // <path><from>source</from><into>/</into></path>\n    public void set(File path) {\n      from = path;\n      into = \"/\";\n    }\n\n    @Override\n    public Path getFrom() {\n      return from.toPath();\n    }\n\n    public void setFrom(File from) {\n      this.from = from;\n    }\n\n    @Override\n    public String getInto() {\n      return into;\n    }\n\n    @Override\n    public List<String> getIncludesList() {\n      return includes;\n    }\n\n    @Override\n    public List<String> getExcludesList() {\n      return excludes;\n    }\n  }\n\n  /** Configuration for the {@code dockerClient} parameter. */\n  public static class DockerClientParameters {\n\n    @Nullable @Parameter private File executable;\n    @Parameter private Map<String, String> environment = Collections.emptyMap();\n  }\n\n  public static class OutputPathsParameters {\n\n    @Nullable @Parameter private File tar;\n    @Nullable @Parameter private File digest;\n    @Nullable @Parameter private File imageId;\n    @Nullable @Parameter private File imageJson;\n  }\n\n  public static class ExtensionParameters implements ExtensionConfiguration {\n\n    @Parameter private String implementation = \"<extension implementation not configured>\";\n    @Parameter private Map<String, String> properties = Collections.emptyMap();\n    @Nullable @Parameter private Object configuration;\n\n    @Override\n    public String getExtensionClass() {\n      return implementation;\n    }\n\n    @Override\n    public Map<String, String> getProperties() {\n      return properties;\n    }\n\n    @Override\n    public Optional<Object> getExtraConfiguration() {\n      return Optional.ofNullable(configuration);\n    }\n  }\n\n  @Nullable\n  @Parameter(defaultValue = \"${session}\", readonly = true)\n  private MavenSession session;\n\n  @Nullable\n  @Parameter(defaultValue = \"${project}\", readonly = true)\n  private MavenProject project;\n\n  @Nullable\n  @Parameter(defaultValue = \"${plugin}\", readonly = true)\n  protected PluginDescriptor descriptor;\n\n  @Component protected SettingsDecrypter settingsDecrypter;\n\n  @Parameter private FromConfiguration from = new FromConfiguration();\n  @Parameter private ToConfiguration to = new ToConfiguration();\n  @Parameter private ContainerParameters container = new ContainerParameters();\n  // this parameter is cloned in FilesMojo\n  @Parameter private ExtraDirectoriesParameters extraDirectories = new ExtraDirectoriesParameters();\n  @Parameter private DockerClientParameters dockerClient = new DockerClientParameters();\n  @Parameter private OutputPathsParameters outputPaths = new OutputPathsParameters();\n\n  @Parameter(property = PropertyNames.ALLOW_INSECURE_REGISTRIES)\n  private boolean allowInsecureRegistries;\n\n  @Parameter(property = PropertyNames.CONTAINERIZING_MODE)\n  private String containerizingMode = \"exploded\";\n\n  @Parameter(property = PropertyNames.SKIP)\n  private boolean skip;\n\n  @Parameter private List<ExtensionParameters> pluginExtensions = Collections.emptyList();\n  @Inject private Set<JibMavenPluginExtension<?>> injectedPluginExtensions = Collections.emptySet();\n\n  protected Set<JibMavenPluginExtension<?>> getInjectedPluginExtensions() {\n    return injectedPluginExtensions;\n  }\n\n  protected MavenSession getSession() {\n    return Preconditions.checkNotNull(session);\n  }\n\n  protected MavenProject getProject() {\n    return Preconditions.checkNotNull(project);\n  }\n\n  protected void checkJibVersion() throws MojoExecutionException {\n    Preconditions.checkNotNull(descriptor);\n    MojoCommon.checkJibVersion(descriptor);\n  }\n\n  /**\n   * Gets the specified platforms.\n   *\n   * @return the specified platforms\n   */\n  List<PlatformParameters> getPlatforms() {\n    String property = getProperty(PropertyNames.FROM_PLATFORMS);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property).stream()\n          .map(PlatformParameters::of)\n          .collect(Collectors.toList());\n    }\n    return from.platforms;\n  }\n\n  /**\n   * Gets the base image reference.\n   *\n   * @return the configured base image reference\n   */\n  @Nullable\n  String getBaseImage() {\n    String property = getProperty(PropertyNames.FROM_IMAGE);\n    if (property != null) {\n      return property;\n    }\n    return from.image;\n  }\n\n  /**\n   * Gets the base image credential helper configuration.\n   *\n   * @return configuration for the base image credential helper\n   */\n  CredHelperConfiguration getBaseImageCredHelperConfig() {\n    String property = getProperty(PropertyNames.FROM_CRED_HELPER);\n    if (property != null) {\n      from.credHelper.setHelper(property);\n    }\n    return from.credHelper;\n  }\n\n  AuthConfiguration getBaseImageAuth() {\n    // System/pom properties for auth are handled in ConfigurationPropertyValidator\n    return from.auth;\n  }\n\n  /**\n   * Gets the target image reference.\n   *\n   * @return the configured target image reference\n   */\n  @Nullable\n  protected String getTargetImage() {\n    String propertyAlternate = getProperty(PropertyNames.TO_IMAGE_ALTERNATE);\n    if (propertyAlternate != null) {\n      return propertyAlternate;\n    }\n    String property = getProperty(PropertyNames.TO_IMAGE);\n    if (property != null) {\n      return property;\n    }\n    return to.image;\n  }\n\n  /**\n   * Gets the additional target image tags.\n   *\n   * @return the configured extra tags.\n   */\n  Set<String> getTargetImageAdditionalTags() {\n    String property = getProperty(PropertyNames.TO_TAGS);\n    List<String> tags =\n        property != null ? ConfigurationPropertyValidator.parseListProperty(property) : to.tags;\n    if (tags.stream().anyMatch(Strings::isNullOrEmpty)) {\n      String source = property != null ? PropertyNames.TO_TAGS : \"<to><tags>\";\n      throw new IllegalArgumentException(source + \" has empty tag\");\n    }\n    return new HashSet<>(tags);\n  }\n\n  /**\n   * Gets the target image credential helper configuration.\n   *\n   * @return configuration for the target image credential helper\n   */\n  CredHelperConfiguration getTargetImageCredentialHelperConfig() {\n    String property = getProperty(PropertyNames.TO_CRED_HELPER);\n    if (property != null) {\n      to.credHelper.setHelper(property);\n    }\n    return to.credHelper;\n  }\n\n  AuthConfiguration getTargetImageAuth() {\n    // System/pom properties for auth are handled in ConfigurationPropertyValidator\n    return to.auth;\n  }\n\n  /**\n   * Gets the configured entrypoint.\n   *\n   * @return the configured entrypoint\n   */\n  @Nullable\n  List<String> getEntrypoint() {\n    String property = getProperty(PropertyNames.CONTAINER_ENTRYPOINT);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property);\n    }\n    return container.entrypoint;\n  }\n\n  /**\n   * Gets the configured jvm flags.\n   *\n   * @return the configured jvm flags\n   */\n  List<String> getJvmFlags() {\n    String property = getProperty(PropertyNames.CONTAINER_JVM_FLAGS);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property);\n    }\n    return container.jvmFlags;\n  }\n\n  /**\n   * Gets the configured environment variables.\n   *\n   * @return the configured environment variables\n   */\n  Map<String, String> getEnvironment() {\n    String property = getProperty(PropertyNames.CONTAINER_ENVIRONMENT);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseMapProperty(property);\n    }\n    return container.environment;\n  }\n\n  /**\n   * Gets the extra classpath elements.\n   *\n   * @return the extra classpath elements\n   */\n  List<String> getExtraClasspath() {\n    String property = getProperty(PropertyNames.CONTAINER_EXTRA_CLASSPATH);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property);\n    }\n    return container.extraClasspath;\n  }\n\n  /**\n   * Returns whether to expand classpath dependencies.\n   *\n   * @return {@code true} to expand classpath dependencies. {@code false} otherwise.\n   */\n  public boolean getExpandClasspathDependencies() {\n    String property = getProperty(PropertyNames.EXPAND_CLASSPATH_DEPENDENCIES);\n    if (property != null) {\n      return Boolean.valueOf(property);\n    }\n    return container.expandClasspathDependencies;\n  }\n\n  /**\n   * Gets the name of the main class.\n   *\n   * @return the configured main class name\n   */\n  @Nullable\n  String getMainClass() {\n    String property = getProperty(PropertyNames.CONTAINER_MAIN_CLASS);\n    if (property != null) {\n      return property;\n    }\n    return container.mainClass;\n  }\n\n  /**\n   * Gets the username or UID which the process in the container should run as.\n   *\n   * @return the configured main class name\n   */\n  @Nullable\n  String getUser() {\n    String property = getProperty(PropertyNames.CONTAINER_USER);\n    if (property != null) {\n      return property;\n    }\n    return container.user;\n  }\n\n  /**\n   * Gets the working directory in the container.\n   *\n   * @return the working directory\n   */\n  @Nullable\n  String getWorkingDirectory() {\n    String property = getProperty(PropertyNames.CONTAINER_WORKING_DIRECTORY);\n    if (property != null) {\n      return property;\n    }\n    return container.workingDirectory;\n  }\n\n  /**\n   * Gets the configured main arguments.\n   *\n   * @return the configured main arguments\n   */\n  @Nullable\n  List<String> getArgs() {\n    String property = getProperty(PropertyNames.CONTAINER_ARGS);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property);\n    }\n    return container.args;\n  }\n\n  /**\n   * Gets the configured exposed ports.\n   *\n   * @return the configured exposed ports\n   */\n  List<String> getExposedPorts() {\n    String property = getProperty(PropertyNames.CONTAINER_PORTS);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property);\n    }\n    return container.ports;\n  }\n\n  /**\n   * Gets the configured volumes.\n   *\n   * @return the configured volumes\n   */\n  List<String> getVolumes() {\n    String property = getProperty(PropertyNames.CONTAINER_VOLUMES);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseListProperty(property);\n    }\n    return container.volumes;\n  }\n\n  /**\n   * Gets the configured labels.\n   *\n   * @return the configured labels\n   */\n  Map<String, String> getLabels() {\n    String property = getProperty(PropertyNames.CONTAINER_LABELS);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseMapProperty(property);\n    }\n    return container.labels;\n  }\n\n  /**\n   * Gets the configured app root directory.\n   *\n   * @return the configured app root directory\n   */\n  String getAppRoot() {\n    String property = getProperty(PropertyNames.CONTAINER_APP_ROOT);\n    if (property != null) {\n      return property;\n    }\n    return container.appRoot;\n  }\n\n  /**\n   * Gets the configured container image format.\n   *\n   * @return the configured container image format\n   */\n  String getFormat() {\n    String property = getProperty(PropertyNames.CONTAINER_FORMAT);\n    if (property != null) {\n      return property;\n    }\n    return container.format;\n  }\n\n  /**\n   * Gets the configured files modification time value.\n   *\n   * @return the configured files modification time value\n   */\n  String getFilesModificationTime() {\n    String property = getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME);\n    if (property != null) {\n      return property;\n    }\n    return container.filesModificationTime;\n  }\n\n  /**\n   * Gets the configured container creation time value.\n   *\n   * @return the configured container creation time value\n   */\n  String getCreationTime() {\n    String property = getProperty(PropertyNames.CONTAINER_CREATION_TIME);\n    if (property != null) {\n      return property;\n    }\n    return container.creationTime;\n  }\n\n  /**\n   * Gets the list of configured extra directory paths.\n   *\n   * @return the list of configured extra directory paths\n   */\n  List<ExtraDirectoryParameters> getExtraDirectories() {\n    // TODO: Should inform user about nonexistent directory if using custom directory.\n    String property = getProperty(PropertyNames.EXTRA_DIRECTORIES_PATHS);\n    if (property != null) {\n      List<String> paths = ConfigurationPropertyValidator.parseListProperty(property);\n      return paths.stream()\n          .map(path -> new ExtraDirectoryParameters(new File(path), \"/\"))\n          .collect(Collectors.toList());\n    }\n    return extraDirectories.getPaths();\n  }\n\n  /**\n   * Gets the configured extra layer file permissions.\n   *\n   * @return the configured extra layer file permissions\n   */\n  List<PermissionConfiguration> getExtraDirectoryPermissions() {\n    String property = getProperty(PropertyNames.EXTRA_DIRECTORIES_PERMISSIONS);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseMapProperty(property).entrySet().stream()\n          .map(entry -> new PermissionConfiguration(entry.getKey(), entry.getValue()))\n          .collect(Collectors.toList());\n    }\n    return extraDirectories.permissions;\n  }\n\n  @Nullable\n  Path getDockerClientExecutable() {\n    String property = getProperty(PropertyNames.DOCKER_CLIENT_EXECUTABLE);\n    if (property != null) {\n      return Paths.get(property);\n    }\n    return dockerClient.executable == null ? null : dockerClient.executable.toPath();\n  }\n\n  Map<String, String> getDockerClientEnvironment() {\n    String property = getProperty(PropertyNames.DOCKER_CLIENT_ENVIRONMENT);\n    if (property != null) {\n      return ConfigurationPropertyValidator.parseMapProperty(property);\n    }\n    return dockerClient.environment;\n  }\n\n  Path getTarOutputPath() {\n    Path configuredPath =\n        outputPaths.tar == null\n            ? Paths.get(getProject().getBuild().getDirectory()).resolve(\"jib-image.tar\")\n            : outputPaths.tar.toPath();\n    return getRelativeToProjectRoot(configuredPath, PropertyNames.OUTPUT_PATHS_TAR);\n  }\n\n  Path getDigestOutputPath() {\n    Path configuredPath =\n        outputPaths.digest == null\n            ? Paths.get(getProject().getBuild().getDirectory()).resolve(\"jib-image.digest\")\n            : outputPaths.digest.toPath();\n    return getRelativeToProjectRoot(configuredPath, PropertyNames.OUTPUT_PATHS_DIGEST);\n  }\n\n  Path getImageIdOutputPath() {\n    Path configuredPath =\n        outputPaths.imageId == null\n            ? Paths.get(getProject().getBuild().getDirectory()).resolve(\"jib-image.id\")\n            : outputPaths.imageId.toPath();\n    return getRelativeToProjectRoot(configuredPath, PropertyNames.OUTPUT_PATHS_IMAGE_ID);\n  }\n\n  Path getImageJsonOutputPath() {\n    Path configuredPath =\n        outputPaths.imageJson == null\n            ? Paths.get(getProject().getBuild().getDirectory()).resolve(\"jib-image.json\")\n            : outputPaths.imageJson.toPath();\n    return getRelativeToProjectRoot(configuredPath, PropertyNames.OUTPUT_PATHS_IMAGE_JSON);\n  }\n\n  private Path getRelativeToProjectRoot(Path configuration, String propertyName) {\n    String property = getProperty(propertyName);\n    Path path = property != null ? Paths.get(property) : configuration;\n    return path.isAbsolute() ? path : getProject().getBasedir().toPath().resolve(path);\n  }\n\n  boolean getAllowInsecureRegistries() {\n    return allowInsecureRegistries;\n  }\n\n  public String getContainerizingMode() {\n    String property = getProperty(PropertyNames.CONTAINERIZING_MODE);\n    return property != null ? property : containerizingMode;\n  }\n\n  boolean isSkipped() {\n    return skip;\n  }\n\n  List<ExtensionParameters> getPluginExtensions() {\n    return pluginExtensions;\n  }\n\n  /**\n   * Return false if the `jib.containerize` property is specified and does not match this\n   * module/project. Used by the Skaffold-Jib binding.\n   *\n   * @return true if this module should be containerized\n   */\n  boolean isContainerizable() {\n    String moduleSpecification = getProperty(PropertyNames.CONTAINERIZE);\n    if (project == null || Strings.isNullOrEmpty(moduleSpecification)) {\n      return true;\n    }\n    // modules can be specified in one of three ways:\n    // 1) a `groupId:artifactId`\n    // 2) an `:artifactId`\n    // 3) relative path within the repository\n    if (moduleSpecification.equals(project.getGroupId() + \":\" + project.getArtifactId())\n        || moduleSpecification.equals(\":\" + project.getArtifactId())) {\n      return true;\n    }\n    // Relative paths never have a colon on *nix nor Windows.  This moduleSpecification could be an\n    // :artifactId or groupId:artifactId for a different artifact.\n    if (moduleSpecification.contains(\":\")) {\n      return false;\n    }\n    try {\n      Path projectBase = project.getBasedir().toPath();\n      return projectBase.endsWith(moduleSpecification);\n    } catch (InvalidPathException ex) {\n      // ignore since moduleSpecification may not actually be a path\n      return false;\n    }\n  }\n\n  SettingsDecrypter getSettingsDecrypter() {\n    return settingsDecrypter;\n  }\n\n  @VisibleForTesting\n  void setProject(MavenProject project) {\n    this.project = project;\n  }\n\n  @VisibleForTesting\n  void setSession(MavenSession session) {\n    this.session = session;\n  }\n\n  @Nullable\n  String getProperty(String propertyName) {\n    return MavenProjectProperties.getProperty(propertyName, project, session);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenExtensionData.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.maven.extension.MavenData;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.project.MavenProject;\n\n/** Maven-specific data and properties to supply to plugin extensions. */\nclass MavenExtensionData implements MavenData {\n\n  private final MavenProject project;\n  private final MavenSession session;\n\n  MavenExtensionData(MavenProject project, MavenSession session) {\n    this.project = project;\n    this.session = session;\n  }\n\n  @Override\n  public MavenProject getMavenProject() {\n    return project;\n  }\n\n  @Override\n  public MavenSession getMavenSession() {\n    return session;\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenHelpfulSuggestions.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions;\n\n/** Maven-specific {@link HelpfulSuggestions}. */\nclass MavenHelpfulSuggestions extends HelpfulSuggestions {\n\n  MavenHelpfulSuggestions(String messagePrefix) {\n    super(messagePrefix, \"mvn clean\", \"<to><image>\", \"-Dimage\", \"pom.xml\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.event.events.ProgressEvent;\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport com.google.cloud.tools.jib.event.progress.ProgressEventHandler;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension;\nimport com.google.cloud.tools.jib.plugins.common.ContainerizingMode;\nimport com.google.cloud.tools.jib.plugins.common.JavaContainerBuilderHelper;\nimport com.google.cloud.tools.jib.plugins.common.PluginExtensionLogger;\nimport com.google.cloud.tools.jib.plugins.common.ProjectProperties;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.TimerEventHandler;\nimport com.google.cloud.tools.jib.plugins.common.ZipUtil;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLogger;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLoggerBuilder;\nimport com.google.cloud.tools.jib.plugins.common.logging.ProgressDisplayGenerator;\nimport com.google.cloud.tools.jib.plugins.common.logging.SingleThreadedExecutor;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.cloud.tools.jib.plugins.extension.NullExtension;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Verify;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.ServiceLoader;\nimport java.util.Set;\nimport java.util.function.Predicate;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.apache.maven.artifact.Artifact;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.model.Build;\nimport org.apache.maven.model.Plugin;\nimport org.apache.maven.model.PluginExecution;\nimport org.apache.maven.plugin.descriptor.PluginDescriptor;\nimport org.apache.maven.plugin.logging.Log;\nimport org.apache.maven.project.MavenProject;\nimport org.apache.maven.shared.utils.Os;\nimport org.codehaus.plexus.util.xml.Xpp3Dom;\n\n/** Obtains information about a {@link MavenProject}. */\npublic class MavenProjectProperties implements ProjectProperties {\n\n  /** Used for logging during main class inference and analysis of user configuration. */\n  public static final String PLUGIN_NAME = \"jib-maven-plugin\";\n\n  /** Used to identify this plugin when interacting with the maven system. */\n  public static final String PLUGIN_KEY = \"com.google.cloud.tools:\" + PLUGIN_NAME;\n\n  /** Used to generate the User-Agent header and history metadata. */\n  private static final String TOOL_NAME = \"jib-maven-plugin\";\n\n  /** Used for logging during main class inference. */\n  private static final String JAR_PLUGIN_NAME = \"'maven-jar-plugin'\";\n\n  private static final Duration LOGGING_THREAD_SHUTDOWN_TIMEOUT = Duration.ofSeconds(1);\n\n  /**\n   * Static factory method for {@link MavenProjectProperties}.\n   *\n   * @param jibPluginDescriptor the jib-maven-plugin plugin descriptor\n   * @param project the {@link MavenProject} for the plugin.\n   * @param session the {@link MavenSession} for the plugin.\n   * @param log the Maven {@link Log} to log messages during Jib execution\n   * @param tempDirectoryProvider temporary directory provider\n   * @param injectedExtensions the extensions injected into the Mojo\n   * @return a MavenProjectProperties from the given project and logger.\n   */\n  public static MavenProjectProperties getForProject(\n      PluginDescriptor jibPluginDescriptor,\n      MavenProject project,\n      MavenSession session,\n      Log log,\n      TempDirectoryProvider tempDirectoryProvider,\n      Collection<JibMavenPluginExtension<?>> injectedExtensions) {\n    Preconditions.checkNotNull(jibPluginDescriptor);\n    Supplier<List<JibMavenPluginExtension<?>>> extensionLoader =\n        () -> {\n          List<JibMavenPluginExtension<?>> extensions = new ArrayList<>();\n          for (JibMavenPluginExtension<?> extension :\n              ServiceLoader.load(JibMavenPluginExtension.class)) {\n            extensions.add(extension);\n          }\n          return extensions;\n        };\n    return new MavenProjectProperties(\n        jibPluginDescriptor,\n        project,\n        session,\n        log,\n        tempDirectoryProvider,\n        injectedExtensions,\n        extensionLoader);\n  }\n\n  /**\n   * Gets a property with the given name. First checks for a Maven user property (-D commandline\n   * argument), then checks for a property defined in the POM, then checks for a Maven system\n   * property, then returns null if none of those are defined.\n   *\n   * @param propertyName the name of the property\n   * @param project the Maven project\n   * @param session the Maven session\n   * @return the value of the property, or null if not defined\n   */\n  @Nullable\n  public static String getProperty(\n      String propertyName, @Nullable MavenProject project, @Nullable MavenSession session) {\n    if (session != null && session.getUserProperties().containsKey(propertyName)) {\n      return session.getUserProperties().getProperty(propertyName);\n    }\n    if (project != null && project.getProperties().containsKey(propertyName)) {\n      return project.getProperties().getProperty(propertyName);\n    }\n    if (session != null && session.getSystemProperties().containsKey(propertyName)) {\n      return session.getSystemProperties().getProperty(propertyName);\n    }\n    return null;\n  }\n\n  @VisibleForTesting\n  static boolean isProgressFooterEnabled(MavenSession session) {\n    if (!session.getRequest().isInteractiveMode()) {\n      return false;\n    }\n\n    if (\"plain\".equals(System.getProperty(PropertyNames.CONSOLE))) {\n      return false;\n    }\n\n    // Enables progress footer when ANSI is supported (Windows or System.console() not null and TERM\n    // not 'dumb').\n    if (Os.isFamily(Os.FAMILY_WINDOWS)) {\n      return true;\n    }\n    return System.console() != null && !\"dumb\".equals(System.getenv(\"TERM\"));\n  }\n\n  /**\n   * Gets the major version number from a Java version string.\n   *\n   * <p>Examples: {@code \"1.7\" -> 7, \"1.8.0_161\" -> 8, \"10\" -> 10, \"11.0.1\" -> 11}\n   *\n   * @param versionString the string to convert\n   * @return the major version number as an integer, or 0 if the string is invalid\n   */\n  @VisibleForTesting\n  static int getVersionFromString(String versionString) {\n    // Parse version starting with \"1.\"\n    if (versionString.startsWith(\"1.\")) {\n      if (versionString.length() >= 3 && Character.isDigit(versionString.charAt(2))) {\n        return versionString.charAt(2) - '0';\n      }\n      return 0;\n    }\n\n    // Parse string starting with major version number\n    int dotIndex = versionString.indexOf(\".\");\n    try {\n      if (dotIndex == -1) {\n        return Integer.parseInt(versionString);\n      }\n      return Integer.parseInt(versionString.substring(0, versionString.indexOf(\".\")));\n    } catch (NumberFormatException ex) {\n      return 0;\n    }\n  }\n\n  @VisibleForTesting\n  static Optional<String> getChildValue(@Nullable Xpp3Dom dom, String... childNodePath) {\n    if (dom == null) {\n      return Optional.empty();\n    }\n\n    Xpp3Dom node = dom;\n    for (String child : childNodePath) {\n      node = node.getChild(child);\n      if (node == null) {\n        return Optional.empty();\n      }\n    }\n    return Optional.ofNullable(node.getValue());\n  }\n\n  private final PluginDescriptor jibPluginDescriptor;\n  private final MavenProject project;\n  private final MavenSession session;\n  private final SingleThreadedExecutor singleThreadedExecutor = new SingleThreadedExecutor();\n  private final ConsoleLogger consoleLogger;\n  private final TempDirectoryProvider tempDirectoryProvider;\n  private final Collection<JibMavenPluginExtension<?>> injectedExtensions;\n  private final Supplier<List<JibMavenPluginExtension<?>>> extensionLoader;\n\n  @VisibleForTesting\n  MavenProjectProperties(\n      PluginDescriptor jibPluginDescriptor,\n      MavenProject project,\n      MavenSession session,\n      Log log,\n      TempDirectoryProvider tempDirectoryProvider,\n      Collection<JibMavenPluginExtension<?>> injectedExtensions,\n      Supplier<List<JibMavenPluginExtension<?>>> extensionLoader) {\n    this.jibPluginDescriptor = jibPluginDescriptor;\n    this.project = project;\n    this.session = session;\n    this.tempDirectoryProvider = tempDirectoryProvider;\n    this.injectedExtensions = injectedExtensions;\n    this.extensionLoader = extensionLoader;\n    ConsoleLoggerBuilder consoleLoggerBuilder =\n        (isProgressFooterEnabled(session)\n                ? ConsoleLoggerBuilder.rich(singleThreadedExecutor, true)\n                : ConsoleLoggerBuilder.plain(singleThreadedExecutor).progress(log::info))\n            .lifecycle(log::info);\n    if (log.isDebugEnabled()) {\n      consoleLoggerBuilder\n          .debug(log::debug)\n          // INFO messages also go to Log#debug since Log#info is used for LIFECYCLE.\n          .info(log::debug);\n    }\n    if (log.isWarnEnabled()) {\n      consoleLoggerBuilder.warn(log::warn);\n    }\n    if (log.isErrorEnabled()) {\n      consoleLoggerBuilder.error(log::error);\n    }\n    consoleLogger = consoleLoggerBuilder.build();\n  }\n\n  @Override\n  public JibContainerBuilder createJibContainerBuilder(\n      JavaContainerBuilder javaContainerBuilder, ContainerizingMode containerizingMode)\n      throws IOException {\n    try {\n      if (isWarProject()) {\n        Path war = getWarArtifact();\n        Path explodedWarPath = tempDirectoryProvider.newDirectory();\n        ZipUtil.unzip(war, explodedWarPath);\n        return JavaContainerBuilderHelper.fromExplodedWar(\n            javaContainerBuilder,\n            explodedWarPath,\n            getProjectDependencies().stream()\n                .map(Artifact::getFile)\n                .map(File::getName)\n                .collect(Collectors.toSet()));\n      }\n\n      switch (containerizingMode) {\n        case EXPLODED:\n          // Add resources, and classes\n          Path classesOutputDirectory = Paths.get(project.getBuild().getOutputDirectory());\n          // Don't use Path.endsWith(), since Path works on path elements.\n          Predicate<Path> isClassFile = path -> path.getFileName().toString().endsWith(\".class\");\n          javaContainerBuilder\n              .addResources(classesOutputDirectory, isClassFile.negate())\n              .addClasses(classesOutputDirectory, isClassFile);\n          break;\n\n        case PACKAGED:\n          // Add a JAR\n          javaContainerBuilder.addToClasspath(getJarArtifact());\n          break;\n\n        default:\n          throw new IllegalStateException(\"unknown containerizing mode: \" + containerizingMode);\n      }\n\n      // Classify and add dependencies\n      Map<LayerType, List<Path>> classifiedDependencies =\n          classifyDependencies(project.getArtifacts(), getProjectDependencies());\n\n      javaContainerBuilder.addDependencies(\n          Preconditions.checkNotNull(classifiedDependencies.get(LayerType.DEPENDENCIES)));\n      javaContainerBuilder.addSnapshotDependencies(\n          Preconditions.checkNotNull(classifiedDependencies.get(LayerType.SNAPSHOT_DEPENDENCIES)));\n      javaContainerBuilder.addProjectDependencies(\n          Preconditions.checkNotNull(classifiedDependencies.get(LayerType.PROJECT_DEPENDENCIES)));\n      return javaContainerBuilder.toContainerBuilder();\n\n    } catch (IOException ex) {\n      throw new IOException(\n          \"Obtaining project build output files failed; make sure you have \"\n              + (isPackageErrorMessage(containerizingMode) ? \"packaged\" : \"compiled\")\n              + \" your project \"\n              + \"before trying to build the image. (Did you accidentally run \\\"mvn clean \"\n              + \"jib:build\\\" instead of \\\"mvn clean \"\n              + (isPackageErrorMessage(containerizingMode) ? \"package\" : \"compile\")\n              + \" jib:build\\\"?)\",\n          ex);\n    }\n  }\n\n  @VisibleForTesting\n  Set<Artifact> getProjectDependencies() {\n    return session.getProjects().stream()\n        .map(MavenProject::getArtifact)\n        .filter(artifact -> !artifact.equals(project.getArtifact()))\n        .filter(artifact -> artifact.getFile() != null)\n        .collect(Collectors.toSet());\n  }\n\n  @VisibleForTesting\n  Map<LayerType, List<Path>> classifyDependencies(\n      Set<Artifact> dependencies, Set<Artifact> projectArtifacts) {\n    Map<LayerType, List<Path>> classifiedDependencies = new HashMap<>();\n    classifiedDependencies.put(LayerType.DEPENDENCIES, new ArrayList<>());\n    classifiedDependencies.put(LayerType.SNAPSHOT_DEPENDENCIES, new ArrayList<>());\n    classifiedDependencies.put(LayerType.PROJECT_DEPENDENCIES, new ArrayList<>());\n\n    for (Artifact artifact : dependencies) {\n      if (projectArtifacts.contains(artifact)) {\n        classifiedDependencies.get(LayerType.PROJECT_DEPENDENCIES).add(artifact.getFile().toPath());\n      } else if (artifact.isSnapshot()) {\n        classifiedDependencies\n            .get(LayerType.SNAPSHOT_DEPENDENCIES)\n            .add(artifact.getFile().toPath());\n      } else {\n        classifiedDependencies.get(LayerType.DEPENDENCIES).add(artifact.getFile().toPath());\n      }\n    }\n    return classifiedDependencies;\n  }\n\n  @Override\n  public List<Path> getClassFiles() throws IOException {\n    return new DirectoryWalker(Paths.get(project.getBuild().getOutputDirectory())).walk();\n  }\n\n  @Override\n  public List<Path> getDependencies() {\n    return project.getArtifacts().stream()\n        .map(artifact -> artifact.getFile().toPath())\n        .collect(Collectors.toList());\n  }\n\n  @Override\n  public void waitForLoggingThread() {\n    singleThreadedExecutor.shutDownAndAwaitTermination(LOGGING_THREAD_SHUTDOWN_TIMEOUT);\n  }\n\n  @Override\n  public void configureEventHandlers(Containerizer containerizer) {\n    containerizer\n        .addEventHandler(LogEvent.class, this::log)\n        .addEventHandler(\n            TimerEvent.class, new TimerEventHandler(message -> log(LogEvent.debug(message))))\n        .addEventHandler(\n            ProgressEvent.class,\n            new ProgressEventHandler(\n                update ->\n                    consoleLogger.setFooter(\n                        ProgressDisplayGenerator.generateProgressDisplay(\n                            update.getProgress(), update.getUnfinishedLeafTasks()))));\n  }\n\n  @Override\n  public void log(LogEvent logEvent) {\n    consoleLogger.log(logEvent.getLevel(), logEvent.getMessage());\n  }\n\n  @Override\n  public String getToolName() {\n    return TOOL_NAME;\n  }\n\n  @Override\n  public String getToolVersion() {\n    return jibPluginDescriptor.getVersion();\n  }\n\n  @Override\n  public String getPluginName() {\n    return PLUGIN_NAME;\n  }\n\n  @Nullable\n  @Override\n  public String getMainClassFromJarPlugin() {\n    Plugin mavenJarPlugin = project.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\");\n    if (mavenJarPlugin != null) {\n      return getChildValue(\n              (Xpp3Dom) mavenJarPlugin.getConfiguration(), \"archive\", \"manifest\", \"mainClass\")\n          .orElse(null);\n    }\n    return null;\n  }\n\n  @Override\n  public Path getDefaultCacheDirectory() {\n    return Paths.get(project.getBuild().getDirectory(), CACHE_DIRECTORY_NAME);\n  }\n\n  @Override\n  public String getJarPluginName() {\n    return JAR_PLUGIN_NAME;\n  }\n\n  /**\n   * Gets whether or not the given project is a war project. This is the case for projects with\n   * packaging {@code war} and {@code gwt-app}.\n   *\n   * @return {@code true} if the project is a war project, {@code false} if not\n   */\n  @Override\n  public boolean isWarProject() {\n    String packaging = project.getPackaging();\n    return \"war\".equals(packaging) || \"gwt-app\".equals(packaging);\n  }\n\n  @Override\n  public String getName() {\n    return project.getArtifactId();\n  }\n\n  @Override\n  public String getVersion() {\n    return project.getVersion();\n  }\n\n  @Override\n  public int getMajorJavaVersion() {\n    // Check properties for version\n    if (project.getProperties().getProperty(\"maven.compiler.target\") != null) {\n      return getVersionFromString(project.getProperties().getProperty(\"maven.compiler.target\"));\n    }\n    if (project.getProperties().getProperty(\"maven.compiler.release\") != null) {\n      return getVersionFromString(project.getProperties().getProperty(\"maven.compiler.release\"));\n    }\n\n    // Check maven-compiler-plugin for version\n    Plugin mavenCompilerPlugin =\n        project.getPlugin(\"org.apache.maven.plugins:maven-compiler-plugin\");\n    if (mavenCompilerPlugin != null) {\n      Xpp3Dom pluginConfiguration = (Xpp3Dom) mavenCompilerPlugin.getConfiguration();\n      Optional<String> target = getChildValue(pluginConfiguration, \"target\");\n      if (target.isPresent()) {\n        return getVersionFromString(target.get());\n      }\n      Optional<String> release = getChildValue(pluginConfiguration, \"release\");\n      if (release.isPresent()) {\n        return getVersionFromString(release.get());\n      }\n    }\n    return 6; // maven-compiler-plugin default is 1.6\n  }\n\n  @Override\n  public boolean isOffline() {\n    return session.isOffline();\n  }\n\n  @VisibleForTesting\n  Path getWarArtifact() {\n    Build build = project.getBuild();\n    String warName = build.getFinalName();\n\n    Plugin warPlugin = project.getPlugin(\"org.apache.maven.plugins:maven-war-plugin\");\n    if (warPlugin != null) {\n      for (PluginExecution execution : warPlugin.getExecutions()) {\n        if (\"default-war\".equals(execution.getId())) {\n          Xpp3Dom configuration = (Xpp3Dom) execution.getConfiguration();\n          warName = getChildValue(configuration, \"warName\").orElse(warName);\n        }\n      }\n    }\n\n    return Paths.get(build.getDirectory(), warName + \".war\");\n  }\n\n  /**\n   * Gets the path of the JAR that the Maven JAR Plugin generates. Will also make copies of jar\n   * files with non-conforming names like those produced by springboot -- myjar.jar.original ->\n   * myjar.original.jar.\n   *\n   * <p>https://maven.apache.org/plugins/maven-jar-plugin/jar-mojo.html\n   * https://github.com/apache/maven-jar-plugin/blob/80f58a84aacff6e671f5a601d62a3a3800b507dc/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java#L177\n   *\n   * @return the path of the JAR\n   * @throws IOException if copying jars with non-conforming names fails\n   */\n  @VisibleForTesting\n  Path getJarArtifact() throws IOException {\n    Optional<String> classifier = Optional.empty();\n    Path buildDirectory = Paths.get(project.getBuild().getDirectory());\n    Path outputDirectory = buildDirectory;\n\n    // Read <classifier> and <outputDirectory> from maven-jar-plugin.\n    Plugin jarPlugin = project.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\");\n    if (jarPlugin != null) {\n      for (PluginExecution execution : jarPlugin.getExecutions()) {\n        if (\"default-jar\".equals(execution.getId())) {\n          Xpp3Dom configuration = (Xpp3Dom) execution.getConfiguration();\n          classifier = getChildValue(configuration, \"classifier\");\n          Optional<String> directoryString = getChildValue(configuration, \"outputDirectory\");\n\n          if (directoryString.isPresent()) {\n            outputDirectory = project.getBasedir().toPath().resolve(directoryString.get());\n          }\n          break;\n        }\n      }\n    }\n\n    String finalName = project.getBuild().getFinalName();\n    String suffix = \".jar\";\n\n    Optional<Xpp3Dom> bootConfiguration = getSpringBootRepackageConfiguration();\n    if (bootConfiguration.isPresent()) {\n      log(LogEvent.lifecycle(\"Spring Boot repackaging (fat JAR) detected; using the original JAR\"));\n\n      // Spring renames original JAR only when replacing it, so check if the paths are clashing.\n      Optional<String> bootFinalName = getChildValue(bootConfiguration.get(), \"finalName\");\n      Optional<String> bootClassifier = getChildValue(bootConfiguration.get(), \"classifier\");\n\n      boolean sameDirectory = outputDirectory.equals(buildDirectory);\n      // If Boot <finalName> is undefined, it uses the default project <finalName>.\n      boolean sameFinalName = !bootFinalName.isPresent() || finalName.equals(bootFinalName.get());\n      boolean sameClassifier = classifier.equals(bootClassifier);\n      if (sameDirectory && sameFinalName && sameClassifier) {\n        suffix = \".jar.original\";\n      }\n    }\n\n    String noSuffixJarName = finalName + (classifier.isPresent() ? '-' + classifier.get() : \"\");\n    Path jarPath = outputDirectory.resolve(noSuffixJarName + suffix);\n    log(LogEvent.debug(\"Using JAR: \" + jarPath));\n\n    if (\".jar\".equals(suffix)) {\n      return jarPath;\n    }\n\n    // \"*\" in \"java -cp *\" doesn't work if JAR doesn't end with \".jar\". Copy the JAR with a new name\n    // ending with \".jar\".\n    Path tempDirectory = tempDirectoryProvider.newDirectory();\n    Path newJarPath = tempDirectory.resolve(noSuffixJarName + \".original.jar\");\n    Files.copy(jarPath, newJarPath);\n    return newJarPath;\n  }\n\n  /**\n   * Returns Spring Boot {@code <configuration>} if the Spring Boot plugin is configured to run the\n   * {@code repackage} goal to create a Spring Boot artifact.\n   */\n  @VisibleForTesting\n  Optional<Xpp3Dom> getSpringBootRepackageConfiguration() {\n    Plugin springBootPlugin =\n        project.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\");\n    if (springBootPlugin != null) {\n      for (PluginExecution execution : springBootPlugin.getExecutions()) {\n        if (execution.getGoals().contains(\"repackage\")) {\n          Xpp3Dom configuration = (Xpp3Dom) execution.getConfiguration();\n          if (configuration == null) {\n            return Optional.of(new Xpp3Dom(\"configuration\"));\n          }\n\n          boolean skip = Boolean.parseBoolean(getChildValue(configuration, \"skip\").orElse(\"false\"));\n          return skip ? Optional.empty() : Optional.of(configuration);\n        }\n      }\n    }\n    return Optional.empty();\n  }\n\n  @Override\n  public JibContainerBuilder runPluginExtensions(\n      List<? extends ExtensionConfiguration> extensionConfigs,\n      JibContainerBuilder jibContainerBuilder)\n      throws JibPluginExtensionException {\n    if (extensionConfigs.isEmpty()) {\n      log(LogEvent.debug(\"No Jib plugin extensions configured to load\"));\n      return jibContainerBuilder;\n    }\n\n    // Add the injected extensions at first to prefer them over the ones from JDK service\n    // loader.\n    // Extensions might support both approaches (injection and JDK service loader) at the same\n    // time for compatibility reasons.\n    List<JibMavenPluginExtension<?>> loadedExtensions = new ArrayList<>(injectedExtensions);\n    loadedExtensions.addAll(extensionLoader.get());\n    JibMavenPluginExtension<?> extension = null;\n    ContainerBuildPlan buildPlan = jibContainerBuilder.toContainerBuildPlan();\n    try {\n      for (ExtensionConfiguration config : extensionConfigs) {\n        extension = findConfiguredExtension(loadedExtensions, config);\n\n        log(LogEvent.lifecycle(\"Running extension: \" + config.getExtensionClass()));\n        buildPlan =\n            runPluginExtension(extension.getExtraConfigType(), extension, config, buildPlan);\n        ImageReference.parse(buildPlan.getBaseImage()); // to validate image reference\n      }\n      return jibContainerBuilder.applyContainerBuildPlan(buildPlan);\n\n    } catch (InvalidImageReferenceException ex) {\n      throw new JibPluginExtensionException(\n          Verify.verifyNotNull(extension).getClass(),\n          \"invalid base image reference: \" + buildPlan.getBaseImage(),\n          ex);\n    }\n  }\n\n  // Unchecked casting: \"getExtraConfiguration()\" (Optional<Object>) to Object<T> and \"extension\"\n  // (JibMavenPluginExtension<?>) to JibMavenPluginExtension<T> where T is the extension-defined\n  // config type (as requested by \"JibMavenPluginExtension.getExtraConfigType()\").\n  @SuppressWarnings({\"unchecked\"})\n  private <T> ContainerBuildPlan runPluginExtension(\n      Optional<Class<T>> extraConfigType,\n      JibMavenPluginExtension<?> extension,\n      ExtensionConfiguration config,\n      ContainerBuildPlan buildPlan)\n      throws JibPluginExtensionException {\n    Optional<T> extraConfig = Optional.empty();\n    Optional<Object> configs = config.getExtraConfiguration();\n    if (configs.isPresent()) {\n      if (!extraConfigType.isPresent()) {\n        throw new IllegalArgumentException(\n            \"extension \"\n                + extension.getClass().getSimpleName()\n                + \" does not expect extension-specific configuration; remove the inapplicable \"\n                + \"<pluginExtension><configuration> from pom.xml\");\n      } else if (!extraConfigType.get().isInstance(configs.get())) {\n        throw new JibPluginExtensionException(\n            extension.getClass(),\n            \"extension-specific <configuration> for \"\n                + extension.getClass().getSimpleName()\n                + \" is not of type \"\n                + extraConfigType.get().getName()\n                + \" but \"\n                + configs.get().getClass().getName()\n                + \"; specify the correct type with <pluginExtension><configuration \"\n                + \"implementation=\\\"\"\n                + extraConfigType.get().getName()\n                + \"\\\">\");\n      } else {\n        // configs is of type Optional, so this cast always succeeds\n        // without the isInstance() check above. (Note generic <T> is erased at runtime.)\n        extraConfig = (Optional<T>) configs;\n      }\n    }\n\n    try {\n      return ((JibMavenPluginExtension<T>) extension)\n          .extendContainerBuildPlan(\n              buildPlan,\n              config.getProperties(),\n              extraConfig,\n              new MavenExtensionData(project, session),\n              new PluginExtensionLogger(this::log));\n    } catch (RuntimeException ex) {\n      throw new JibPluginExtensionException(\n          extension.getClass(), \"extension crashed: \" + ex.getMessage(), ex);\n    }\n  }\n\n  private JibMavenPluginExtension<?> findConfiguredExtension(\n      List<JibMavenPluginExtension<?>> extensions, ExtensionConfiguration config)\n      throws JibPluginExtensionException {\n    Predicate<JibMavenPluginExtension<?>> matchesClassName =\n        extension -> extension.getClass().getName().equals(config.getExtensionClass());\n    Optional<JibMavenPluginExtension<?>> found =\n        extensions.stream().filter(matchesClassName).findFirst();\n    if (!found.isPresent()) {\n      throw new JibPluginExtensionException(\n          NullExtension.class,\n          \"extension configured but not discovered on Jib runtime classpath: \"\n              + config.getExtensionClass());\n    }\n    return found.get();\n  }\n\n  private boolean isPackageErrorMessage(ContainerizingMode containerizingMode) {\n    return containerizingMode == ContainerizingMode.PACKAGED || isWarProject();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\n/** Maven-specific adapter for providing raw configuration parameter values. */\npublic class MavenRawConfiguration implements RawConfiguration {\n\n  private final JibPluginConfiguration jibPluginConfiguration;\n\n  /**\n   * Creates a raw configuration instances.\n   *\n   * @param jibPluginConfiguration the Jib plugin configuration\n   */\n  public MavenRawConfiguration(JibPluginConfiguration jibPluginConfiguration) {\n    this.jibPluginConfiguration = jibPluginConfiguration;\n  }\n\n  @Override\n  public Optional<String> getFromImage() {\n    return Optional.ofNullable(jibPluginConfiguration.getBaseImage());\n  }\n\n  @Override\n  public AuthProperty getFromAuth() {\n    return jibPluginConfiguration.getBaseImageAuth();\n  }\n\n  @Override\n  public CredHelperConfiguration getFromCredHelper() {\n    return jibPluginConfiguration.getBaseImageCredHelperConfig();\n  }\n\n  @Override\n  public Optional<String> getToImage() {\n    return Optional.ofNullable(jibPluginConfiguration.getTargetImage());\n  }\n\n  @Override\n  public AuthProperty getToAuth() {\n    return jibPluginConfiguration.getTargetImageAuth();\n  }\n\n  @Override\n  public CredHelperConfiguration getToCredHelper() {\n    return jibPluginConfiguration.getTargetImageCredentialHelperConfig();\n  }\n\n  @Override\n  public Set<String> getToTags() {\n    return jibPluginConfiguration.getTargetImageAdditionalTags();\n  }\n\n  @Override\n  public Optional<List<String>> getEntrypoint() {\n    return Optional.ofNullable(jibPluginConfiguration.getEntrypoint());\n  }\n\n  @Override\n  public Optional<List<String>> getProgramArguments() {\n    return Optional.ofNullable(jibPluginConfiguration.getArgs());\n  }\n\n  @Override\n  public List<String> getExtraClasspath() {\n    return jibPluginConfiguration.getExtraClasspath();\n  }\n\n  @Override\n  public boolean getExpandClasspathDependencies() {\n    return jibPluginConfiguration.getExpandClasspathDependencies();\n  }\n\n  @Override\n  public Optional<String> getMainClass() {\n    return Optional.ofNullable(jibPluginConfiguration.getMainClass());\n  }\n\n  @Override\n  public List<String> getJvmFlags() {\n    return jibPluginConfiguration.getJvmFlags();\n  }\n\n  @Override\n  public String getAppRoot() {\n    return jibPluginConfiguration.getAppRoot();\n  }\n\n  @Override\n  public Map<String, String> getEnvironment() {\n    return jibPluginConfiguration.getEnvironment();\n  }\n\n  @Override\n  public Map<String, String> getLabels() {\n    return jibPluginConfiguration.getLabels();\n  }\n\n  @Override\n  public List<String> getVolumes() {\n    return jibPluginConfiguration.getVolumes();\n  }\n\n  @Override\n  public List<String> getPorts() {\n    return jibPluginConfiguration.getExposedPorts();\n  }\n\n  @Override\n  public Optional<String> getUser() {\n    return Optional.ofNullable(jibPluginConfiguration.getUser());\n  }\n\n  @Override\n  public Optional<String> getWorkingDirectory() {\n    return Optional.ofNullable(jibPluginConfiguration.getWorkingDirectory());\n  }\n\n  @Override\n  public boolean getAllowInsecureRegistries() {\n    return jibPluginConfiguration.getAllowInsecureRegistries();\n  }\n\n  @Override\n  public ImageFormat getImageFormat() {\n    return ImageFormat.valueOf(jibPluginConfiguration.getFormat());\n  }\n\n  @Override\n  public Optional<String> getProperty(String propertyName) {\n    return Optional.ofNullable(jibPluginConfiguration.getProperty(propertyName));\n  }\n\n  @Override\n  public String getFilesModificationTime() {\n    return jibPluginConfiguration.getFilesModificationTime();\n  }\n\n  @Override\n  public String getCreationTime() {\n    return jibPluginConfiguration.getCreationTime();\n  }\n\n  @Override\n  public List<? extends ExtraDirectoriesConfiguration> getExtraDirectories() {\n    return MojoCommon.getExtraDirectories(jibPluginConfiguration);\n  }\n\n  @Override\n  public Map<String, FilePermissions> getExtraDirectoryPermissions() {\n    return MojoCommon.convertPermissionsList(jibPluginConfiguration.getExtraDirectoryPermissions());\n  }\n\n  @Override\n  public Optional<Path> getDockerExecutable() {\n    return Optional.ofNullable(jibPluginConfiguration.getDockerClientExecutable());\n  }\n\n  @Override\n  public Map<String, String> getDockerEnvironment() {\n    return jibPluginConfiguration.getDockerClientEnvironment();\n  }\n\n  @Override\n  public String getContainerizingMode() {\n    return jibPluginConfiguration.getContainerizingMode();\n  }\n\n  @Override\n  public Path getTarOutputPath() {\n    return jibPluginConfiguration.getTarOutputPath();\n  }\n\n  @Override\n  public Path getDigestOutputPath() {\n    return jibPluginConfiguration.getDigestOutputPath();\n  }\n\n  @Override\n  public Path getImageIdOutputPath() {\n    return jibPluginConfiguration.getImageIdOutputPath();\n  }\n\n  @Override\n  public Path getImageJsonOutputPath() {\n    return jibPluginConfiguration.getImageJsonOutputPath();\n  }\n\n  @Override\n  public List<? extends ExtensionConfiguration> getPluginExtensions() {\n    return jibPluginConfiguration.getPluginExtensions();\n  }\n\n  @Override\n  public List<? extends PlatformConfiguration> getPlatforms() {\n    return jibPluginConfiguration.getPlatforms();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenSettingsProxyProvider.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableList;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.Nullable;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.settings.Proxy;\nimport org.apache.maven.settings.Settings;\nimport org.apache.maven.settings.building.SettingsProblem;\nimport org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;\nimport org.apache.maven.settings.crypto.SettingsDecrypter;\nimport org.apache.maven.settings.crypto.SettingsDecryptionRequest;\nimport org.apache.maven.settings.crypto.SettingsDecryptionResult;\n\n/** Propagates proxy configuration from Maven settings to system properties. */\nclass MavenSettingsProxyProvider {\n\n  private static final ImmutableList<String> PROXY_PROPERTIES =\n      ImmutableList.of(\"proxyHost\", \"proxyPort\", \"proxyUser\", \"proxyPassword\");\n\n  private MavenSettingsProxyProvider() {}\n\n  /**\n   * Initializes proxy settings based on Maven settings if they are not already set by the user\n   * directly.\n   *\n   * @param settings Maven settings\n   */\n  static void activateHttpAndHttpsProxies(Settings settings, SettingsDecrypter decrypter)\n      throws MojoExecutionException {\n    List<Proxy> proxies = new ArrayList<>(2);\n    for (String protocol : ImmutableList.of(\"http\", \"https\")) {\n      if (areProxyPropertiesSet(protocol)) {\n        continue;\n      }\n      settings.getProxies().stream()\n          .filter(Proxy::isActive)\n          .filter(proxy -> protocol.equals(proxy.getProtocol()))\n          .findFirst()\n          .ifPresent(proxies::add);\n    }\n\n    if (proxies.isEmpty()) {\n      return;\n    }\n\n    SettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest().setProxies(proxies);\n    SettingsDecryptionResult result = decrypter.decrypt(request);\n\n    for (SettingsProblem problem : result.getProblems()) {\n      if (problem.getSeverity() == SettingsProblem.Severity.ERROR\n          || problem.getSeverity() == SettingsProblem.Severity.FATAL) {\n        throw new MojoExecutionException(\n            \"Unable to decrypt proxy info from settings.xml: \" + problem);\n      }\n    }\n\n    result.getProxies().forEach(MavenSettingsProxyProvider::setProxyProperties);\n  }\n\n  /**\n   * Set proxy system properties based on Maven proxy configuration.\n   *\n   * @param proxy Maven proxy settings\n   */\n  @VisibleForTesting\n  static void setProxyProperties(Proxy proxy) {\n    String protocol = proxy.getProtocol();\n\n    setPropertySafe(protocol + \".proxyHost\", proxy.getHost());\n    setPropertySafe(protocol + \".proxyPort\", String.valueOf(proxy.getPort()));\n    setPropertySafe(protocol + \".proxyUser\", proxy.getUsername());\n    setPropertySafe(protocol + \".proxyPassword\", proxy.getPassword());\n    setPropertySafe(\"http.nonProxyHosts\", proxy.getNonProxyHosts());\n  }\n\n  private static void setPropertySafe(String property, @Nullable String value) {\n    if (value != null) {\n      System.setProperty(property, value);\n    }\n  }\n\n  /**\n   * Check if any proxy system properties are already set for a given protocol. Note, <code>\n   * nonProxyHosts</code> is excluded as it can only be set with <code>http</code>.\n   *\n   * @param protocol protocol\n   */\n  @VisibleForTesting\n  static boolean areProxyPropertiesSet(String protocol) {\n    return PROXY_PROPERTIES.stream()\n        .anyMatch(property -> System.getProperty(protocol + \".\" + property) != null);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenSettingsServerCredentials.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.cloud.tools.jib.plugins.common.InferredAuthException;\nimport com.google.cloud.tools.jib.plugins.common.InferredAuthProvider;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\nimport org.apache.maven.settings.Server;\nimport org.apache.maven.settings.Settings;\nimport org.apache.maven.settings.building.SettingsProblem;\nimport org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;\nimport org.apache.maven.settings.crypto.SettingsDecrypter;\nimport org.apache.maven.settings.crypto.SettingsDecryptionRequest;\nimport org.apache.maven.settings.crypto.SettingsDecryptionResult;\n\n/**\n * Retrieves credentials for servers defined in <a\n * href=\"https://maven.apache.org/settings.html\">Maven settings</a>.\n */\nclass MavenSettingsServerCredentials implements InferredAuthProvider {\n\n  static final String CREDENTIAL_SOURCE = \"Maven settings file\";\n\n  private final Settings settings;\n  private final SettingsDecrypter decrypter;\n\n  /**\n   * Create new instance.\n   *\n   * @param settings decrypted Maven settings\n   */\n  MavenSettingsServerCredentials(Settings settings, SettingsDecrypter decrypter) {\n    this.settings = settings;\n    this.decrypter = decrypter;\n  }\n\n  /**\n   * Retrieves credentials for {@code registry} from Maven settings.\n   *\n   * @param registry the registry\n   * @return the auth info for the registry, or {@link Optional#empty} if none could be retrieved\n   */\n  @Override\n  public Optional<AuthProperty> inferAuth(String registry) throws InferredAuthException {\n\n    Server server = getServerFromMavenSettings(registry);\n    if (server == null) {\n      return Optional.empty();\n    }\n\n    SettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest(server);\n    SettingsDecryptionResult result = decrypter.decrypt(request);\n    // Un-encrypted passwords are passed through, so a problem indicates a real issue.\n    // If there are any ERROR or FATAL problems reported, then decryption failed.\n    for (SettingsProblem problem : result.getProblems()) {\n      if (problem.getSeverity() == SettingsProblem.Severity.ERROR\n          || problem.getSeverity() == SettingsProblem.Severity.FATAL) {\n        throw new InferredAuthException(\n            \"Unable to decrypt server(\" + registry + \") info from settings.xml: \" + problem);\n      }\n    }\n    Server resultServer = result.getServer();\n\n    String username = resultServer.getUsername();\n    String password = resultServer.getPassword();\n\n    return Optional.of(\n        new AuthProperty() {\n\n          @Override\n          public String getUsername() {\n            return username;\n          }\n\n          @Override\n          public String getPassword() {\n            return password;\n          }\n\n          @Override\n          public String getAuthDescriptor() {\n            return CREDENTIAL_SOURCE;\n          }\n\n          @Override\n          public String getUsernameDescriptor() {\n            return CREDENTIAL_SOURCE;\n          }\n\n          @Override\n          public String getPasswordDescriptor() {\n            return CREDENTIAL_SOURCE;\n          }\n        });\n  }\n\n  @Nullable\n  @VisibleForTesting\n  Server getServerFromMavenSettings(String registry) {\n    Server server = settings.getServer(registry);\n    if (server != null) {\n      return server;\n    }\n\n    // try without port\n    int index = registry.lastIndexOf(':');\n    if (index != -1) {\n      return settings.getServer(registry.substring(0, index));\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MojoCommon.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration.ExtraDirectoryParameters;\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration.PermissionConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.ProjectProperties;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.cloud.tools.jib.plugins.common.UpdateChecker;\nimport com.google.cloud.tools.jib.plugins.common.VersionChecker;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.common.base.Preconditions;\nimport com.google.common.util.concurrent.Futures;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport org.apache.maven.artifact.versioning.DefaultArtifactVersion;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.descriptor.PluginDescriptor;\nimport org.apache.maven.plugin.logging.Log;\nimport org.apache.maven.project.MavenProject;\n\n/** Collection of common methods to share between Maven goals. */\npublic class MojoCommon {\n  /** Describes a minimum required version or version range for Jib. */\n  public static final String REQUIRED_VERSION_PROPERTY_NAME = \"jib.requiredVersion\";\n\n  public static final String VERSION_URL = \"https://storage.googleapis.com/jib-versions/jib-maven\";\n\n  static Future<Optional<String>> newUpdateChecker(\n      ProjectProperties projectProperties, GlobalConfig globalConfig, Log logger) {\n    if (projectProperties.isOffline()\n        || !logger.isInfoEnabled()\n        || globalConfig.isDisableUpdateCheck()) {\n      return Futures.immediateFuture(Optional.empty());\n    }\n    ExecutorService executorService = Executors.newSingleThreadExecutor();\n    try {\n      return UpdateChecker.checkForUpdate(\n          executorService,\n          VERSION_URL,\n          projectProperties.getToolName(),\n          projectProperties.getToolVersion(),\n          projectProperties::log);\n    } finally {\n      executorService.shutdown();\n    }\n  }\n\n  static void finishUpdateChecker(\n      ProjectProperties projectProperties, Future<Optional<String>> updateCheckFuture) {\n    UpdateChecker.finishUpdateCheck(updateCheckFuture)\n        .ifPresent(\n            latestVersion -> {\n              String updateMessage =\n                  String.format(\n                      \"A new version of %s (%s) is available (currently using %s). Update your\"\n                          + \" build configuration to use the latest features and fixes!\",\n                      projectProperties.getToolName(),\n                      latestVersion,\n                      projectProperties.getToolVersion());\n              String privacyUrl = ProjectInfo.GITHUB_URL + \"/blob/master/docs/privacy.md\";\n              String privacyMessage =\n                  String.format(\n                      \"Please see %s for info on disabling this update check.\", privacyUrl);\n              projectProperties.log(LogEvent.lifecycle(\"\"));\n              projectProperties.log(LogEvent.lifecycle(\"\\u001B[33m\" + updateMessage + \"\\u001B[0m\"));\n              projectProperties.log(\n                  LogEvent.lifecycle(\n                      \"\\u001B[33m\"\n                          + ProjectInfo.GITHUB_URL\n                          + \"/blob/master/jib-maven-plugin/CHANGELOG.md\\u001B[0m\"));\n              projectProperties.log(LogEvent.lifecycle(\"\"));\n              projectProperties.log(LogEvent.lifecycle(privacyMessage));\n              projectProperties.log(LogEvent.lifecycle(\"\"));\n            });\n  }\n\n  /**\n   * Gets the list of extra directory paths from a {@link JibPluginConfiguration}. Returns {@code\n   * (project dir)/src/main/jib} by default if not configured.\n   *\n   * @param jibPluginConfiguration the build configuration\n   * @return the list of resolved extra directories\n   */\n  static List<ExtraDirectoryParameters> getExtraDirectories(\n      JibPluginConfiguration jibPluginConfiguration) {\n    List<ExtraDirectoryParameters> extraDirectories = jibPluginConfiguration.getExtraDirectories();\n    if (!extraDirectories.isEmpty()) {\n      for (ExtraDirectoryParameters directory : extraDirectories) {\n        if (directory.getFrom().equals(Paths.get(\"\"))) {\n          throw new IllegalArgumentException(\n              \"Incomplete <extraDirectories><paths> configuration; source directory must be set\");\n        }\n      }\n      return extraDirectories;\n    }\n\n    MavenProject project = Preconditions.checkNotNull(jibPluginConfiguration.getProject());\n    return Collections.singletonList(\n        new ExtraDirectoryParameters(\n            project.getBasedir().toPath().resolve(\"src\").resolve(\"main\").resolve(\"jib\").toFile(),\n            \"/\"));\n  }\n\n  /**\n   * Converts a list of {@link PermissionConfiguration} to an equivalent {@code\n   * String->FilePermission} map.\n   *\n   * @param permissionList the list to convert\n   * @return the resulting map\n   */\n  static Map<String, FilePermissions> convertPermissionsList(\n      List<PermissionConfiguration> permissionList) {\n    // Order is important, so use a LinkedHashMap\n    Map<String, FilePermissions> permissionsMap = new LinkedHashMap<>();\n    for (PermissionConfiguration permission : permissionList) {\n      Optional<String> file = permission.getFile();\n      Optional<String> mode = permission.getMode();\n      if (!file.isPresent() || !mode.isPresent()) {\n        throw new IllegalArgumentException(\n            \"Incomplete <permission> configuration; requires <file> and <mode> fields to be set\");\n      }\n      permissionsMap.put(file.get(), FilePermissions.fromOctalString(mode.get()));\n    }\n    return permissionsMap;\n  }\n\n  /**\n   * Check that the actual version satisfies required Jib version range when specified. No check is\n   * performed if the provided Jib version is {@code null}, which should only occur during debug.\n   *\n   * @param descriptor the plugin version\n   * @throws MojoExecutionException if the version is not acceptable\n   */\n  public static void checkJibVersion(PluginDescriptor descriptor) throws MojoExecutionException {\n    String acceptableVersionSpec = System.getProperty(MojoCommon.REQUIRED_VERSION_PROPERTY_NAME);\n    if (acceptableVersionSpec == null) {\n      return;\n    }\n    String actualVersion = descriptor.getVersion();\n    if (actualVersion == null) {\n      throw new MojoExecutionException(\"Could not determine Jib plugin version\");\n    }\n    VersionChecker<DefaultArtifactVersion> checker =\n        new VersionChecker<>(DefaultArtifactVersion::new);\n    if (!checker.compatibleVersion(acceptableVersionSpec, actualVersion)) {\n      String failure =\n          String.format(\n              \"Jib plugin version is %s but is required to be %s\",\n              actualVersion, acceptableVersionSpec);\n      throw new MojoExecutionException(failure);\n    }\n  }\n\n  /**\n   * Determines if Jib goal execution on this project/module should be skipped due to configuration.\n   *\n   * @param jibPluginConfiguration usually {@code this}, the Mojo this check is applied in.\n   * @return {@code true} if Jib should be skipped (should not execute goal), or {@code false} if it\n   *     should continue with execution.\n   */\n  public static boolean shouldSkipJibExecution(JibPluginConfiguration jibPluginConfiguration) {\n    Log log = jibPluginConfiguration.getLog();\n    if (jibPluginConfiguration.isSkipped()) {\n      log.info(\"Skipping containerization because jib-maven-plugin: skip = true\");\n      return true;\n    }\n    if (!jibPluginConfiguration.isContainerizable()) {\n      log.info(\n          \"Skipping containerization of this module (not specified in \"\n              + PropertyNames.CONTAINERIZE\n              + \")\");\n      return true;\n    }\n    if (\"pom\".equals(jibPluginConfiguration.getProject().getPackaging())) {\n      log.info(\"Skipping containerization because packaging is 'pom'...\");\n      return true;\n    }\n    return false;\n  }\n\n  private MojoCommon() {}\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/CheckJibVersionMojo.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.MojoCommon;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.MojoFailureException;\nimport org.apache.maven.plugins.annotations.LifecyclePhase;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\n\n/**\n * This internal Skaffold-related goal checks that the Jib plugin version is within some specified\n * range. It is only required so that older versions of Jib (prior to the introduction of the {@code\n * jib.requiredVersion} property) will error in such a way that it indicates the jib version is out\n * of date. This goal can be removed once there are no users of Jib prior to 1.4.0.\n *\n * <p>Expected use: {@code mvn jib:_skaffold-fail-if-jib-out-of-date -Djib.requiredVersion='[1.4,2)'\n * jib:build -Dimage=xxx}\n */\n@Mojo(\n    name = CheckJibVersionMojo.GOAL_NAME,\n    requiresProject = false,\n    requiresDependencyCollection = ResolutionScope.NONE,\n    defaultPhase = LifecyclePhase.INITIALIZE)\npublic class CheckJibVersionMojo extends SkaffoldBindingMojo {\n\n  @VisibleForTesting static final String GOAL_NAME = \"_skaffold-fail-if-jib-out-of-date\";\n\n  @Override\n  public void execute() throws MojoExecutionException, MojoFailureException {\n    if (Strings.isNullOrEmpty(System.getProperty(MojoCommon.REQUIRED_VERSION_PROPERTY_NAME))) {\n      throw new MojoExecutionException(\n          GOAL_NAME + \" requires \" + MojoCommon.REQUIRED_VERSION_PROPERTY_NAME + \" to be set\");\n    }\n    checkJibVersion();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.api.client.util.Strings;\nimport com.google.cloud.tools.jib.maven.MavenProjectProperties;\nimport com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldFilesOutput;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.apache.maven.artifact.Artifact;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.model.FileSet;\nimport org.apache.maven.model.Plugin;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.MojoFailureException;\nimport org.apache.maven.plugins.annotations.Component;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.Parameter;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\nimport org.apache.maven.project.DefaultDependencyResolutionRequest;\nimport org.apache.maven.project.DependencyResolutionException;\nimport org.apache.maven.project.DependencyResolutionResult;\nimport org.apache.maven.project.MavenProject;\nimport org.apache.maven.project.ProjectDependenciesResolver;\nimport org.codehaus.plexus.util.xml.Xpp3Dom;\nimport org.eclipse.aether.graph.DependencyFilter;\n\n/**\n * Print out changing source dependencies on a module. In multimodule applications it should be run\n * by activating a single module and its dependent modules. Dependency collection will ignore\n * project level snapshots (sub-modules) unless the user has explicitly installed them (by only\n * requiring dependencyCollection). For use only within skaffold.\n *\n * <p>Expected use: \"./mvnw jib:_skaffold-files-v2 -q\" or \"./mvnw jib:_skaffold-files-v2 -pl module\n * -am -q\"\n */\n@Mojo(\n    name = FilesMojoV2.GOAL_NAME,\n    requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME,\n    aggregator = true)\npublic class FilesMojoV2 extends SkaffoldBindingMojo {\n\n  @VisibleForTesting static final String GOAL_NAME = \"_skaffold-files-v2\";\n\n  // extracting source directories based on https://kotlinlang.org/docs/reference/using-maven.html\n  @VisibleForTesting\n  static Set<Path> getKotlinSourceDirectories(MavenProject project) {\n    Plugin kotlinPlugin = project.getPlugin(\"org.jetbrains.kotlin:kotlin-maven-plugin\");\n    if (kotlinPlugin == null) {\n      return Collections.emptySet();\n    }\n\n    Path projectBaseDir = project.getBasedir().toPath();\n\n    // Extract <sourceDir> values from <configuration> in the plugin <executions>. Sample:\n    // <executions><execution><configuration>\n    //   <sourceDirs>\n    //     <sourceDir>src/main/kotlin</sourceDir>\n    //     <sourceDir>${project.basedir}/src/main/java</sourceDir>\n    //   </sourceDirs>\n    // </configuration></execution></executions>\n    Set<Path> kotlinSourceDirectories =\n        kotlinPlugin.getExecutions().stream()\n            .filter(execution -> !execution.getGoals().contains(\"test-compile\"))\n            .map(execution -> (Xpp3Dom) execution.getConfiguration())\n            .filter(Objects::nonNull)\n            .map(configuration -> configuration.getChild(\"sourceDirs\"))\n            .filter(Objects::nonNull)\n            .map(sourceDirs -> Arrays.asList(sourceDirs.getChildren()))\n            .flatMap(Collection::stream) // \"array of arrays\" into \"arrays\"\n            .map(Xpp3Dom::getValue)\n            .filter(value -> !Strings.isNullOrEmpty(value))\n            .map(Paths::get)\n            .map(path -> path.isAbsolute() ? path : projectBaseDir.resolve(path))\n            .collect(Collectors.toSet());\n\n    Path conventionalDirectory = projectBaseDir.resolve(Paths.get(\"src\", \"main\", \"kotlin\"));\n    kotlinSourceDirectories.add(conventionalDirectory);\n\n    return kotlinSourceDirectories;\n  }\n\n  @Nullable\n  @Parameter(defaultValue = \"${session}\", required = true, readonly = true)\n  private MavenSession session;\n\n  @Nullable\n  @Parameter(defaultValue = \"${reactorProjects}\", required = true, readonly = true)\n  private List<MavenProject> projects;\n\n  // TODO: This is internal maven, we should find a better way to do this\n  @Nullable @Component private ProjectDependenciesResolver projectDependenciesResolver;\n\n  private final SkaffoldFilesOutput skaffoldFilesOutput = new SkaffoldFilesOutput();\n\n  @Override\n  public void execute() throws MojoExecutionException, MojoFailureException {\n    Preconditions.checkNotNull(projects);\n    Preconditions.checkNotNull(session);\n    Preconditions.checkNotNull(projectDependenciesResolver);\n    checkJibVersion();\n\n    for (MavenProject project : projects) {\n      // Add pom configuration files\n      skaffoldFilesOutput.addBuild(project.getFile().toPath());\n      if (\"pom\".equals(project.getPackaging())) {\n        // done if <packaging>pom</packaging>\n        continue;\n      }\n\n      // Add sources directory (resolved by maven to be an absolute path)\n      skaffoldFilesOutput.addInput(Paths.get(project.getBuild().getSourceDirectory()));\n\n      for (Path directory : getKotlinSourceDirectories(project)) {\n        skaffoldFilesOutput.addInput(directory);\n      }\n\n      // Add resources directory (resolved by maven to be an absolute path)\n      project.getBuild().getResources().stream()\n          .map(FileSet::getDirectory)\n          .map(Paths::get)\n          .forEach(skaffoldFilesOutput::addInput);\n\n      // This seems weird, but we will only print out the jib \"extraFiles\" directory on projects\n      // where the plugin is explicitly configured (even though _skaffold-files-v2 is a\n      // jib-maven-plugin goal and is expected to run on all projects irrespective of their\n      // configuring of the jib plugin).\n      if (project.getPlugin(MavenProjectProperties.PLUGIN_KEY) != null) {\n        // Add extra directory\n        resolveExtraDirectories(project).forEach(skaffoldFilesOutput::addInput);\n      }\n\n      // See above note on \"extraFiles\"\n      SkaffoldConfiguration.Watch watch = collectWatchParameters(project);\n      resolveFiles(watch.buildIncludes, project).forEach(skaffoldFilesOutput::addBuild);\n      resolveFiles(watch.includes, project).forEach(skaffoldFilesOutput::addInput);\n      // we don't do any special pre-processing for ignore (input and ignore can overlap with exact\n      // matches)\n      resolveFiles(watch.excludes, project).forEach(skaffoldFilesOutput::addIgnore);\n\n      // Grab non-project SNAPSHOT dependencies for this project\n      // TODO: this whole sections relies on internal maven API, it could break. We need to explore\n      // TODO: better ways to resolve dependencies using the public maven API.\n      Set<String> projectArtifacts =\n          projects.stream()\n              .map(MavenProject::getArtifact)\n              .map(Artifact::toString)\n              .collect(Collectors.toSet());\n\n      DependencyFilter ignoreProjectDependenciesFilter =\n          (node, parents) -> {\n            if (node == null || node.getDependency() == null) {\n              // if nothing, then ignore\n              return false;\n            }\n            if (projectArtifacts.contains(node.getArtifact().toString())) {\n              // ignore project dependency artifacts\n              return false;\n            }\n            // we only want compile/runtime deps\n            return Artifact.SCOPE_COMPILE_PLUS_RUNTIME.contains(node.getDependency().getScope());\n          };\n\n      try {\n        DependencyResolutionResult resolutionResult =\n            projectDependenciesResolver.resolve(\n                new DefaultDependencyResolutionRequest(project, session.getRepositorySession())\n                    .setResolutionFilter(ignoreProjectDependenciesFilter));\n        resolutionResult.getDependencies().stream()\n            .map(org.eclipse.aether.graph.Dependency::getArtifact)\n            .filter(org.eclipse.aether.artifact.Artifact::isSnapshot)\n            .map(org.eclipse.aether.artifact.Artifact::getFile)\n            .map(File::toPath)\n            .forEach(skaffoldFilesOutput::addInput);\n\n      } catch (DependencyResolutionException ex) {\n        throw new MojoExecutionException(\"Failed to resolve dependencies\", ex);\n      }\n    }\n\n    try {\n      // Print JSON string\n      System.out.println();\n      System.out.println(\"BEGIN JIB JSON\");\n      System.out.println(skaffoldFilesOutput.getJsonString());\n    } catch (IOException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex);\n    }\n  }\n\n  private List<Path> resolveExtraDirectories(MavenProject project) {\n    return collectExtraDirectories(project).stream()\n        .map(path -> path.isAbsolute() ? path : project.getBasedir().toPath().resolve(path))\n        .collect(Collectors.toList());\n  }\n\n  private List<Path> collectExtraDirectories(MavenProject project) {\n    // Try getting extra directory from project/session properties\n    String property =\n        MavenProjectProperties.getProperty(PropertyNames.EXTRA_DIRECTORIES_PATHS, project, session);\n    if (property != null) {\n      List<String> paths = ConfigurationPropertyValidator.parseListProperty(property);\n      return paths.stream().map(Paths::get).collect(Collectors.toList());\n    }\n\n    // Try getting extra directory from project pom\n    Plugin jibMavenPlugin = project.getPlugin(MavenProjectProperties.PLUGIN_KEY);\n    if (jibMavenPlugin != null) {\n      Xpp3Dom pluginConfiguration = (Xpp3Dom) jibMavenPlugin.getConfiguration();\n      if (pluginConfiguration != null) {\n        Xpp3Dom extraDirectoriesConfiguration = pluginConfiguration.getChild(\"extraDirectories\");\n        if (extraDirectoriesConfiguration != null) {\n          Xpp3Dom paths = extraDirectoriesConfiguration.getChild(\"paths\");\n          if (paths != null) {\n            // <extraDirectories><paths><path>...</path><path>...</path></paths></extraDirectories>\n            // paths can contain either strings or ExtraDirectory objects\n            List<Path> pathList = new ArrayList<>();\n            for (Xpp3Dom path : paths.getChildren()) {\n              Xpp3Dom from = path.getChild(\"from\");\n              if (from != null) {\n                pathList.add(Paths.get(from.getValue()));\n              } else {\n                pathList.add(Paths.get(path.getValue()));\n              }\n            }\n            return Collections.unmodifiableList(pathList);\n          }\n        }\n      }\n    }\n\n    // Return default if not found\n    Path projectBase = Preconditions.checkNotNull(project).getBasedir().getAbsoluteFile().toPath();\n    Path srcMainJib = Paths.get(\"src\", \"main\", \"jib\");\n    return Collections.singletonList(projectBase.resolve(srcMainJib));\n  }\n\n  private SkaffoldConfiguration.Watch collectWatchParameters(MavenProject project) {\n    // Try getting extra directory from project pom\n    SkaffoldConfiguration.Watch watchConfig = new SkaffoldConfiguration.Watch();\n    Plugin jibMavenPlugin = project.getPlugin(MavenProjectProperties.PLUGIN_KEY);\n    if (jibMavenPlugin != null) {\n      Xpp3Dom pluginConfiguration = (Xpp3Dom) jibMavenPlugin.getConfiguration();\n      if (pluginConfiguration != null) {\n        Xpp3Dom skaffold = pluginConfiguration.getChild(\"skaffold\");\n        if (skaffold != null) {\n          Xpp3Dom watch = skaffold.getChild(\"watch\");\n          if (watch != null) {\n            Xpp3Dom buildIncludes = watch.getChild(\"buildIncludes\");\n            if (buildIncludes != null) {\n              watchConfig.buildIncludes = xpp3ToList(buildIncludes, File::new);\n            }\n            Xpp3Dom includes = watch.getChild(\"includes\");\n            if (includes != null) {\n              watchConfig.includes = xpp3ToList(includes, File::new);\n            }\n            Xpp3Dom excludes = watch.getChild(\"excludes\");\n            if (excludes != null) {\n              watchConfig.excludes = xpp3ToList(excludes, File::new);\n            }\n          }\n        }\n      }\n    }\n    return watchConfig;\n  }\n\n  private List<Path> resolveFiles(List<File> files, MavenProject project) {\n    return files.stream()\n        .map(File::toPath)\n        .map(path -> path.isAbsolute() ? path : project.getBasedir().toPath().resolve(path))\n        .collect(Collectors.toList());\n  }\n\n  private <T> List<T> xpp3ToList(Xpp3Dom node, Function<String, T> converter) {\n    Preconditions.checkNotNull(node);\n    return Arrays.stream(node.getChildren())\n        .map(Xpp3Dom::getValue)\n        .map(converter)\n        .collect(Collectors.toList());\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/InitMojo.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldInitOutput;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\nimport org.apache.maven.project.MavenProject;\n\n/**\n * Prints out to.image configuration and project name, used for Jib project detection in Skaffold.\n *\n * <p>Expected use: {@code ./mvnw jib:_skaffold-init -q}\n */\n@Mojo(name = InitMojo.GOAL_NAME, requiresDependencyCollection = ResolutionScope.NONE)\npublic class InitMojo extends JibPluginConfiguration {\n\n  @VisibleForTesting static final String GOAL_NAME = \"_skaffold-init\";\n\n  @Override\n  public void execute() throws MojoExecutionException {\n    checkJibVersion();\n    MavenProject project = getProject();\n    // Ignore pom projects\n    if (\"pom\".equals(project.getPackaging())) {\n      return;\n    }\n\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput();\n    skaffoldInitOutput.setImage(getTargetImage());\n    skaffoldInitOutput.setProject(project.getGroupId() + \":\" + project.getArtifactId());\n    System.out.println();\n    System.out.println(\"BEGIN JIB JSON\");\n    try {\n      System.out.println(skaffoldInitOutput.getJsonString());\n    } catch (IOException ex) {\n      throw new MojoExecutionException(ex.getMessage(), ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/PackageGoalsMojo.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.MavenProjectProperties;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport javax.annotation.Nullable;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.lifecycle.LifecycleExecutor;\nimport org.apache.maven.lifecycle.MavenExecutionPlan;\nimport org.apache.maven.plugin.MojoExecution;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.MojoFailureException;\nimport org.apache.maven.plugins.annotations.Component;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.Parameter;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\n\n/**\n * Print out all jib goals tied to the package phase. Useful in multimodule situations to determine\n * if the correct jib goals are configured when running skaffold. For use only within skaffold.\n *\n * <p>It is intended to be used from the root project and only in multimodule situations:\n *\n * <p>./mvnw jib:_skaffold-package-goals -q -pl module [-Pprofile]\n */\n@Mojo(\n    name = PackageGoalsMojo.GOAL_NAME,\n    requiresDirectInvocation = true,\n    requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)\npublic class PackageGoalsMojo extends SkaffoldBindingMojo {\n\n  @VisibleForTesting static final String GOAL_NAME = \"_skaffold-package-goals\";\n\n  @Nullable @Component private LifecycleExecutor lifecycleExecutor;\n\n  @Nullable\n  @Parameter(defaultValue = \"${session}\", readonly = true)\n  private MavenSession session;\n\n  @Override\n  public void execute() throws MojoExecutionException, MojoFailureException {\n    Preconditions.checkNotNull(lifecycleExecutor);\n    Preconditions.checkNotNull(session);\n    checkJibVersion();\n\n    MavenExecutionPlan mavenExecutionPlan;\n    try {\n      mavenExecutionPlan = lifecycleExecutor.calculateExecutionPlan(session, \"package\");\n    } catch (Exception ex) {\n      throw new MojoExecutionException(\"failed to calculate execution plan\", ex);\n    }\n\n    mavenExecutionPlan.getMojoExecutions().stream()\n        .filter(mojoExecution -> \"package\".equals(mojoExecution.getLifecyclePhase()))\n        .filter(\n            mojoExecution ->\n                MavenProjectProperties.PLUGIN_NAME.equals(\n                    mojoExecution.getPlugin().getArtifactId()))\n        .map(MojoExecution::getGoal)\n        .forEach(System.out::println);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/SkaffoldBindingMojo.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.MojoCommon;\nimport com.google.common.base.Preconditions;\nimport javax.annotation.Nullable;\nimport org.apache.maven.plugin.AbstractMojo;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugin.descriptor.PluginDescriptor;\nimport org.apache.maven.plugins.annotations.Parameter;\n\n/** Base class for Skaffold-related goals. */\nabstract class SkaffoldBindingMojo extends AbstractMojo {\n  @Nullable\n  @Parameter(defaultValue = \"${plugin}\", readonly = true)\n  protected PluginDescriptor descriptor;\n\n  protected void checkJibVersion() throws MojoExecutionException {\n    Preconditions.checkNotNull(descriptor);\n    MojoCommon.checkJibVersion(descriptor);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/SkaffoldConfiguration.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.List;\nimport org.apache.maven.plugins.annotations.Parameter;\n\n/** Skaffold specific Jib plugin configuration options. */\npublic class SkaffoldConfiguration {\n\n  /** Skaffold specific Jib plugin configuration for files to watch. */\n  public static class Watch {\n    @Parameter List<File> buildIncludes = Collections.emptyList();\n    @Parameter List<File> includes = Collections.emptyList();\n    @Parameter List<File> excludes = Collections.emptyList();\n  }\n\n  /** Skaffold specific Jib plugin configuration for files to sync. */\n  public static class Sync {\n    @Parameter List<File> excludes = Collections.emptyList();\n  }\n\n  /**\n   * Watch is unused, but left here to define how to parse it. See {@link\n   * FilesMojoV2#collectWatchParameters}\n   */\n  @Parameter Watch watch = new Watch();\n\n  @Parameter Sync sync = new Sync();\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/skaffold/SyncMapMojo.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration;\nimport com.google.cloud.tools.jib.maven.MavenProjectProperties;\nimport com.google.cloud.tools.jib.maven.MavenRawConfiguration;\nimport com.google.cloud.tools.jib.maven.MojoCommon;\nimport com.google.cloud.tools.jib.plugins.common.ContainerizingMode;\nimport com.google.cloud.tools.jib.plugins.common.InvalidContainerizingModeException;\nimport com.google.cloud.tools.jib.plugins.common.PluginConfigurationProcessor;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.util.stream.Collectors;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.plugins.annotations.Mojo;\nimport org.apache.maven.plugins.annotations.Parameter;\nimport org.apache.maven.plugins.annotations.ResolutionScope;\n\n@Mojo(\n    name = SyncMapMojo.GOAL_NAME,\n    requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)\npublic class SyncMapMojo extends JibPluginConfiguration {\n\n  @VisibleForTesting static final String GOAL_NAME = \"_skaffold-sync-map\";\n\n  @Parameter SkaffoldConfiguration skaffold = new SkaffoldConfiguration();\n\n  @Override\n  public void execute() throws MojoExecutionException {\n    checkJibVersion();\n    if (MojoCommon.shouldSkipJibExecution(this)) {\n      return;\n    }\n\n    // TODO: move these shared checks with SyncMapTask into plugins-common\n    // add check that means this is only for jars\n    if (!\"jar\".equals(getProject().getPackaging())) {\n      throw new MojoExecutionException(\n          \"Skaffold sync is currently only available for 'jar' style Jib projects, but the packaging of \"\n              + getProject().getArtifactId()\n              + \" is '\"\n              + getProject().getPackaging()\n              + \"'\");\n    }\n    // add check for exploded containerization\n    try {\n      if (!ContainerizingMode.EXPLODED.equals(ContainerizingMode.from(getContainerizingMode()))) {\n        throw new MojoExecutionException(\n            \"Skaffold sync is currently only available for Jib projects in 'exploded' containerizing mode, but the containerizing mode of \"\n                + getProject().getArtifactId()\n                + \" is '\"\n                + getContainerizingMode()\n                + \"'\");\n      }\n    } catch (InvalidContainerizingModeException ex) {\n      throw new MojoExecutionException(\"Invalid containerizing mode\", ex);\n    }\n\n    try (TempDirectoryProvider tempDirectoryProvider = new TempDirectoryProvider()) {\n      MavenProjectProperties projectProperties =\n          MavenProjectProperties.getForProject(\n              Preconditions.checkNotNull(descriptor),\n              getProject(),\n              getSession(),\n              getLog(),\n              tempDirectoryProvider,\n              getInjectedPluginExtensions());\n\n      MavenRawConfiguration configuration = new MavenRawConfiguration(this);\n\n      try {\n        String syncMapJson =\n            PluginConfigurationProcessor.getSkaffoldSyncMap(\n                configuration,\n                projectProperties,\n                skaffold.sync.excludes.stream()\n                    .map(File::toPath)\n                    .map(Path::toAbsolutePath)\n                    .collect(Collectors.toSet()));\n\n        System.out.println();\n        System.out.println(\"BEGIN JIB JSON: SYNCMAP/1\");\n        System.out.println(syncMapJson);\n\n      } catch (Exception ex) {\n        throw new MojoExecutionException(\n            \"Failed to generate a Jib file map for sync with Skaffold\", ex);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration.PermissionConfiguration;\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration.PlatformParameters;\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Properties;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.model.Build;\nimport org.apache.maven.plugin.logging.Log;\nimport org.apache.maven.project.MavenProject;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link JibPluginConfiguration}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class JibPluginConfigurationTest {\n\n  private final MavenProject project = new MavenProject();\n  private final Properties sessionUserProperties = new Properties();\n  private final Properties sessionSystemProperties = new Properties();\n  @Mock private MavenSession session;\n  @Mock private Log log;\n  @Mock private Build build;\n  private JibPluginConfiguration testPluginConfiguration;\n\n  @Before\n  public void setup() {\n    when(session.getUserProperties()).thenReturn(sessionUserProperties);\n    when(session.getSystemProperties()).thenReturn(sessionSystemProperties);\n    when(build.getDirectory()).thenReturn(\"/test/directory\");\n    testPluginConfiguration =\n        new JibPluginConfiguration() {\n          @Override\n          public void execute() {}\n\n          @Override\n          public Log getLog() {\n            return log;\n          }\n        };\n    project.setBuild(build);\n    project.setFile(new File(\"/repository/project/pom.xml\")); // sets baseDir\n    testPluginConfiguration.setProject(project);\n    testPluginConfiguration.setSession(session);\n  }\n\n  @Test\n  public void testDefaults() {\n    assertThat(testPluginConfiguration.getPlatforms().get(0).getOsName()).hasValue(\"linux\");\n    assertThat(testPluginConfiguration.getPlatforms().get(0).getArchitectureName())\n        .hasValue(\"amd64\");\n    assertThat(testPluginConfiguration.getAppRoot()).isEmpty();\n    assertThat(testPluginConfiguration.getWorkingDirectory()).isNull();\n    assertThat(testPluginConfiguration.getExtraClasspath()).isEmpty();\n    assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isFalse();\n    assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo(\"exploded\");\n    assertThat(testPluginConfiguration.getFilesModificationTime()).isEqualTo(\"EPOCH_PLUS_SECOND\");\n    assertThat(testPluginConfiguration.getCreationTime()).isEqualTo(\"EPOCH\");\n    assertThat(testPluginConfiguration.getInjectedPluginExtensions()).isEmpty();\n    assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName())\n        .isEqualTo(Optional.empty());\n    assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getEnvironment()).isEmpty();\n    assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName())\n        .isEqualTo(Optional.empty());\n    assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getEnvironment())\n        .isEmpty();\n  }\n\n  @Test\n  public void testSystemProperties() {\n    sessionSystemProperties.put(\"jib.from.image\", \"fromImage\");\n    assertThat(testPluginConfiguration.getBaseImage()).isEqualTo(\"fromImage\");\n    sessionSystemProperties.put(\"jib.from.credHelper\", \"credHelper\");\n    assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName().get())\n        .isEqualTo(\"credHelper\");\n\n    sessionSystemProperties.put(\"jib.from.platforms\", \"linux/amd64,darwin/arm64\");\n    List<PlatformParameters> platforms = testPluginConfiguration.getPlatforms();\n    assertThat(platforms).hasSize(2);\n    assertThat(platforms.get(0).getOsName()).hasValue(\"linux\");\n    assertThat(platforms.get(0).getArchitectureName()).hasValue(\"amd64\");\n    assertThat(platforms.get(1).getOsName()).hasValue(\"darwin\");\n    assertThat(platforms.get(1).getArchitectureName()).hasValue(\"arm64\");\n\n    sessionSystemProperties.put(\"image\", \"toImage\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"toImage\");\n    sessionSystemProperties.remove(\"image\");\n    sessionSystemProperties.put(\"jib.to.image\", \"toImage2\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"toImage2\");\n    sessionSystemProperties.put(\"jib.to.tags\", \"tag1,tag2,tag3\");\n    assertThat(testPluginConfiguration.getTargetImageAdditionalTags())\n        .containsExactly(\"tag1\", \"tag2\", \"tag3\");\n    sessionSystemProperties.put(\"jib.to.credHelper\", \"credHelper\");\n    assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName().get())\n        .isEqualTo(\"credHelper\");\n\n    sessionSystemProperties.put(\"jib.container.appRoot\", \"appRoot\");\n    assertThat(testPluginConfiguration.getAppRoot()).isEqualTo(\"appRoot\");\n    sessionSystemProperties.put(\"jib.container.args\", \"arg1,arg2,arg3\");\n    assertThat(testPluginConfiguration.getArgs()).containsExactly(\"arg1\", \"arg2\", \"arg3\").inOrder();\n    sessionSystemProperties.put(\"jib.container.entrypoint\", \"entry1,entry2,entry3\");\n    assertThat(testPluginConfiguration.getEntrypoint())\n        .containsExactly(\"entry1\", \"entry2\", \"entry3\")\n        .inOrder();\n    sessionSystemProperties.put(\"jib.container.environment\", \"env1=val1,env2=val2\");\n    assertThat(testPluginConfiguration.getEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n    sessionSystemProperties.put(\"jib.container.format\", \"format\");\n    assertThat(testPluginConfiguration.getFormat()).isEqualTo(\"format\");\n    sessionSystemProperties.put(\"jib.container.jvmFlags\", \"flag1,flag2,flag3\");\n    assertThat(testPluginConfiguration.getJvmFlags())\n        .containsExactly(\"flag1\", \"flag2\", \"flag3\")\n        .inOrder();\n    sessionSystemProperties.put(\"jib.container.labels\", \"label1=val1,label2=val2\");\n    assertThat(testPluginConfiguration.getLabels())\n        .containsExactly(\"label1\", \"val1\", \"label2\", \"val2\")\n        .inOrder();\n    sessionSystemProperties.put(\"jib.container.mainClass\", \"main\");\n    assertThat(testPluginConfiguration.getMainClass()).isEqualTo(\"main\");\n    sessionSystemProperties.put(\"jib.container.ports\", \"port1,port2,port3\");\n    assertThat(testPluginConfiguration.getExposedPorts())\n        .containsExactly(\"port1\", \"port2\", \"port3\")\n        .inOrder();\n    ;\n    sessionSystemProperties.put(\"jib.container.user\", \"myUser\");\n    assertThat(testPluginConfiguration.getUser()).isEqualTo(\"myUser\");\n    sessionSystemProperties.put(\"jib.container.workingDirectory\", \"/working/directory\");\n    assertThat(testPluginConfiguration.getWorkingDirectory()).isEqualTo(\"/working/directory\");\n    sessionSystemProperties.put(\"jib.container.filesModificationTime\", \"2011-12-03T22:42:05Z\");\n    assertThat(testPluginConfiguration.getFilesModificationTime())\n        .isEqualTo(\"2011-12-03T22:42:05Z\");\n    sessionSystemProperties.put(\"jib.container.creationTime\", \"2011-12-03T22:42:05Z\");\n    assertThat(testPluginConfiguration.getCreationTime()).isEqualTo(\"2011-12-03T22:42:05Z\");\n    sessionSystemProperties.put(\"jib.container.extraClasspath\", \"/foo,/bar\");\n    assertThat(testPluginConfiguration.getExtraClasspath())\n        .containsExactly(\"/foo\", \"/bar\")\n        .inOrder();\n    sessionSystemProperties.put(\"jib.container.expandClasspathDependencies\", \"true\");\n    assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isTrue();\n    sessionSystemProperties.put(\"jib.containerizingMode\", \"packaged\");\n    assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo(\"packaged\");\n\n    sessionSystemProperties.put(\"jib.dockerClient.executable\", \"test-exec\");\n    assertThat(testPluginConfiguration.getDockerClientExecutable())\n        .isEqualTo(Paths.get(\"test-exec\"));\n    sessionSystemProperties.put(\"jib.dockerClient.environment\", \"env1=val1,env2=val2\");\n    assertThat(testPluginConfiguration.getDockerClientEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n  }\n\n  @Test\n  public void testSystemPropertiesWithInvalidPlatform() {\n    sessionSystemProperties.put(\"jib.from.platforms\", \"linux /amd64\");\n    assertThrows(IllegalArgumentException.class, testPluginConfiguration::getPlatforms);\n  }\n\n  @Test\n  public void testSystemPropertiesExtraDirectories() {\n    sessionSystemProperties.put(\"jib.extraDirectories.paths\", \"custom-jib\");\n    assertThat(testPluginConfiguration.getExtraDirectories()).hasSize(1);\n    assertThat(testPluginConfiguration.getExtraDirectories().get(0).getFrom())\n        .isEqualTo(Paths.get(\"custom-jib\"));\n    assertThat(testPluginConfiguration.getExtraDirectories().get(0).getInto()).isEqualTo(\"/\");\n    sessionSystemProperties.put(\n        \"jib.extraDirectories.permissions\", \"/test/file1=123,/another/file=456\");\n    List<PermissionConfiguration> permissions =\n        testPluginConfiguration.getExtraDirectoryPermissions();\n    assertThat(permissions.get(0).getFile()).hasValue(\"/test/file1\");\n    assertThat(permissions.get(0).getMode()).hasValue(\"123\");\n    assertThat(permissions.get(1).getFile()).hasValue(\"/another/file\");\n    assertThat(permissions.get(1).getMode()).hasValue(\"456\");\n  }\n\n  @Test\n  public void testSystemPropertiesOutputPaths() {\n    // Absolute paths\n    sessionSystemProperties.put(\"jib.outputPaths.digest\", \"/digest/path\");\n    assertThat(testPluginConfiguration.getDigestOutputPath()).isEqualTo(Paths.get(\"/digest/path\"));\n    sessionSystemProperties.put(\"jib.outputPaths.imageId\", \"/id/path\");\n    assertThat(testPluginConfiguration.getImageIdOutputPath()).isEqualTo(Paths.get(\"/id/path\"));\n    sessionSystemProperties.put(\"jib.outputPaths.tar\", \"/tar/path\");\n    assertThat(testPluginConfiguration.getTarOutputPath()).isEqualTo(Paths.get(\"/tar/path\"));\n    // Relative paths\n    sessionSystemProperties.put(\"jib.outputPaths.digest\", \"digest/path\");\n    assertThat(testPluginConfiguration.getDigestOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/digest/path\"));\n    sessionSystemProperties.put(\"jib.outputPaths.imageId\", \"id/path\");\n    assertThat(testPluginConfiguration.getImageIdOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/id/path\"));\n    sessionSystemProperties.put(\"jib.outputPaths.imageJson\", \"json/path\");\n    assertThat(testPluginConfiguration.getImageJsonOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/json/path\"));\n    sessionSystemProperties.put(\"jib.outputPaths.tar\", \"tar/path\");\n    assertThat(testPluginConfiguration.getTarOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/tar/path\"));\n  }\n\n  @Test\n  public void testPomProperties() {\n    project.getProperties().setProperty(\"jib.from.image\", \"fromImage\");\n    assertThat(testPluginConfiguration.getBaseImage()).isEqualTo(\"fromImage\");\n    project.getProperties().setProperty(\"jib.from.credHelper\", \"credHelper\");\n    assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName().get())\n        .isEqualTo(\"credHelper\");\n\n    project.getProperties().setProperty(\"image\", \"toImage\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"toImage\");\n    project.getProperties().remove(\"image\");\n    project.getProperties().setProperty(\"jib.to.image\", \"toImage2\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"toImage2\");\n    project.getProperties().setProperty(\"jib.to.tags\", \"tag1,tag2,tag3\");\n    assertThat(testPluginConfiguration.getTargetImageAdditionalTags())\n        .containsExactly(\"tag1\", \"tag2\", \"tag3\");\n    project.getProperties().setProperty(\"jib.to.credHelper\", \"credHelper\");\n    assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName().get())\n        .isEqualTo(\"credHelper\");\n\n    project.getProperties().setProperty(\"jib.container.appRoot\", \"appRoot\");\n    assertThat(testPluginConfiguration.getAppRoot()).isEqualTo(\"appRoot\");\n    project.getProperties().setProperty(\"jib.container.args\", \"arg1,arg2,arg3\");\n    assertThat(testPluginConfiguration.getArgs()).containsExactly(\"arg1\", \"arg2\", \"arg3\").inOrder();\n    project.getProperties().setProperty(\"jib.container.entrypoint\", \"entry1,entry2,entry3\");\n    assertThat(testPluginConfiguration.getEntrypoint())\n        .containsExactly(\"entry1\", \"entry2\", \"entry3\")\n        .inOrder();\n    project.getProperties().setProperty(\"jib.container.environment\", \"env1=val1,env2=val2\");\n    assertThat(testPluginConfiguration.getEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n    project.getProperties().setProperty(\"jib.container.format\", \"format\");\n    assertThat(testPluginConfiguration.getFormat()).isEqualTo(\"format\");\n    project.getProperties().setProperty(\"jib.container.jvmFlags\", \"flag1,flag2,flag3\");\n    assertThat(testPluginConfiguration.getJvmFlags())\n        .containsExactly(\"flag1\", \"flag2\", \"flag3\")\n        .inOrder();\n    project.getProperties().setProperty(\"jib.container.labels\", \"label1=val1,label2=val2\");\n    assertThat(testPluginConfiguration.getLabels())\n        .containsExactly(\"label1\", \"val1\", \"label2\", \"val2\")\n        .inOrder();\n    project.getProperties().setProperty(\"jib.container.mainClass\", \"main\");\n    assertThat(testPluginConfiguration.getMainClass()).isEqualTo(\"main\");\n    project.getProperties().setProperty(\"jib.container.ports\", \"port1,port2,port3\");\n    assertThat(testPluginConfiguration.getExposedPorts())\n        .containsExactly(\"port1\", \"port2\", \"port3\")\n        .inOrder();\n    project.getProperties().setProperty(\"jib.container.user\", \"myUser\");\n    assertThat(testPluginConfiguration.getUser()).isEqualTo(\"myUser\");\n    project.getProperties().setProperty(\"jib.container.workingDirectory\", \"/working/directory\");\n    assertThat(testPluginConfiguration.getWorkingDirectory()).isEqualTo(\"/working/directory\");\n    project\n        .getProperties()\n        .setProperty(\"jib.container.filesModificationTime\", \"2011-12-03T22:42:05Z\");\n    assertThat(testPluginConfiguration.getFilesModificationTime())\n        .isEqualTo(\"2011-12-03T22:42:05Z\");\n    project.getProperties().setProperty(\"jib.container.creationTime\", \"2011-12-03T22:42:05Z\");\n    assertThat(testPluginConfiguration.getCreationTime()).isEqualTo(\"2011-12-03T22:42:05Z\");\n    project.getProperties().setProperty(\"jib.container.extraClasspath\", \"/foo,/bar\");\n    assertThat(testPluginConfiguration.getExtraClasspath())\n        .containsExactly(\"/foo\", \"/bar\")\n        .inOrder();\n    project.getProperties().setProperty(\"jib.container.expandClasspathDependencies\", \"true\");\n    assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isTrue();\n    project.getProperties().setProperty(\"jib.containerizingMode\", \"packaged\");\n    assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo(\"packaged\");\n\n    project.getProperties().setProperty(\"jib.dockerClient.executable\", \"test-exec\");\n    assertThat(testPluginConfiguration.getDockerClientExecutable())\n        .isEqualTo(Paths.get(\"test-exec\"));\n    project.getProperties().setProperty(\"jib.dockerClient.environment\", \"env1=val1,env2=val2\");\n    assertThat(testPluginConfiguration.getDockerClientEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n  }\n\n  @Test\n  public void testPomPropertiesExtraDirectories() {\n    project.getProperties().setProperty(\"jib.extraDirectories.paths\", \"custom-jib\");\n    assertThat(testPluginConfiguration.getExtraDirectories()).hasSize(1);\n    assertThat(testPluginConfiguration.getExtraDirectories().get(0).getFrom())\n        .isEqualTo(Paths.get(\"custom-jib\"));\n    assertThat(testPluginConfiguration.getExtraDirectories().get(0).getInto()).isEqualTo(\"/\");\n    project\n        .getProperties()\n        .setProperty(\"jib.extraDirectories.permissions\", \"/test/file1=123,/another/file=456\");\n    List<PermissionConfiguration> permissions =\n        testPluginConfiguration.getExtraDirectoryPermissions();\n    assertThat(permissions.get(0).getFile()).hasValue(\"/test/file1\");\n    assertThat(permissions.get(0).getMode()).hasValue(\"123\");\n    assertThat(permissions.get(1).getFile()).hasValue(\"/another/file\");\n    assertThat(permissions.get(1).getMode()).hasValue(\"456\");\n  }\n\n  @Test\n  public void testPomPropertiesOutputPaths() {\n    project.getProperties().setProperty(\"jib.outputPaths.digest\", \"/digest/path\");\n    assertThat(testPluginConfiguration.getDigestOutputPath()).isEqualTo(Paths.get(\"/digest/path\"));\n    project.getProperties().setProperty(\"jib.outputPaths.imageId\", \"/id/path\");\n    assertThat(testPluginConfiguration.getImageIdOutputPath()).isEqualTo(Paths.get(\"/id/path\"));\n    project.getProperties().setProperty(\"jib.outputPaths.imageJson\", \"/json/path\");\n    assertThat(testPluginConfiguration.getImageJsonOutputPath()).isEqualTo(Paths.get(\"/json/path\"));\n    project.getProperties().setProperty(\"jib.outputPaths.tar\", \"tar/path\");\n    assertThat(testPluginConfiguration.getTarOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/tar/path\"));\n  }\n\n  @Test\n  public void testUserProperties() {\n    sessionUserProperties.put(\"jib.from.image\", \"fromImage\");\n    assertThat(testPluginConfiguration.getBaseImage()).isEqualTo(\"fromImage\");\n    sessionUserProperties.put(\"jib.from.credHelper\", \"credHelper\");\n    assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName().get())\n        .isEqualTo(\"credHelper\");\n\n    sessionUserProperties.put(\"jib.from.platforms\", \"linux/amd64,darwin/arm64\");\n    List<PlatformParameters> platforms = testPluginConfiguration.getPlatforms();\n    assertThat(platforms).hasSize(2);\n    assertThat(platforms.get(0).getOsName()).hasValue(\"linux\");\n    assertThat(platforms.get(0).getArchitectureName()).hasValue(\"amd64\");\n    assertThat(platforms.get(1).getOsName()).hasValue(\"darwin\");\n    assertThat(platforms.get(1).getArchitectureName()).hasValue(\"arm64\");\n\n    sessionUserProperties.put(\"image\", \"toImage\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"toImage\");\n    sessionUserProperties.remove(\"image\");\n    sessionUserProperties.put(\"jib.to.image\", \"toImage2\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"toImage2\");\n    sessionUserProperties.put(\"jib.to.tags\", \"tag1,tag2,tag3\");\n    assertThat(testPluginConfiguration.getTargetImageAdditionalTags())\n        .containsExactly(\"tag1\", \"tag2\", \"tag3\");\n    sessionUserProperties.put(\"jib.to.credHelper\", \"credHelper\");\n    assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName().get())\n        .isEqualTo(\"credHelper\");\n\n    sessionUserProperties.put(\"jib.container.appRoot\", \"appRoot\");\n    assertThat(testPluginConfiguration.getAppRoot()).isEqualTo(\"appRoot\");\n    sessionUserProperties.put(\"jib.container.args\", \"arg1,arg2,arg3\");\n    assertThat(testPluginConfiguration.getArgs()).containsExactly(\"arg1\", \"arg2\", \"arg3\").inOrder();\n    sessionUserProperties.put(\"jib.container.entrypoint\", \"entry1,entry2,entry3\");\n    assertThat(testPluginConfiguration.getEntrypoint())\n        .containsExactly(\"entry1\", \"entry2\", \"entry3\")\n        .inOrder();\n    sessionUserProperties.put(\"jib.container.environment\", \"env1=val1,env2=val2\");\n    assertThat(testPluginConfiguration.getEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n    sessionUserProperties.put(\"jib.container.format\", \"format\");\n    assertThat(testPluginConfiguration.getFormat()).isEqualTo(\"format\");\n    sessionUserProperties.put(\"jib.container.jvmFlags\", \"flag1,flag2,flag3\");\n    assertThat(testPluginConfiguration.getJvmFlags())\n        .containsExactly(\"flag1\", \"flag2\", \"flag3\")\n        .inOrder();\n    sessionUserProperties.put(\"jib.container.labels\", \"label1=val1,label2=val2\");\n    assertThat(testPluginConfiguration.getLabels())\n        .containsExactly(\"label1\", \"val1\", \"label2\", \"val2\")\n        .inOrder();\n    sessionUserProperties.put(\"jib.container.mainClass\", \"main\");\n    assertThat(testPluginConfiguration.getMainClass()).isEqualTo(\"main\");\n    sessionUserProperties.put(\"jib.container.ports\", \"port1,port2,port3\");\n    assertThat(testPluginConfiguration.getExposedPorts())\n        .containsExactly(\"port1\", \"port2\", \"port3\")\n        .inOrder();\n    ;\n    sessionUserProperties.put(\"jib.container.user\", \"myUser\");\n    assertThat(testPluginConfiguration.getUser()).isEqualTo(\"myUser\");\n    sessionUserProperties.put(\"jib.container.workingDirectory\", \"/working/directory\");\n    assertThat(testPluginConfiguration.getWorkingDirectory()).isEqualTo(\"/working/directory\");\n    sessionUserProperties.put(\"jib.container.filesModificationTime\", \"2011-12-03T22:42:05Z\");\n    assertThat(testPluginConfiguration.getFilesModificationTime())\n        .isEqualTo(\"2011-12-03T22:42:05Z\");\n    sessionUserProperties.put(\"jib.container.creationTime\", \"2011-12-03T22:42:05Z\");\n    assertThat(testPluginConfiguration.getCreationTime()).isEqualTo(\"2011-12-03T22:42:05Z\");\n    sessionUserProperties.put(\"jib.container.extraClasspath\", \"/foo,/bar\");\n    assertThat(testPluginConfiguration.getExtraClasspath())\n        .containsExactly(\"/foo\", \"/bar\")\n        .inOrder();\n    sessionUserProperties.put(\"jib.container.expandClasspathDependencies\", \"true\");\n    assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isTrue();\n    sessionUserProperties.put(\"jib.containerizingMode\", \"packaged\");\n    assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo(\"packaged\");\n\n    sessionUserProperties.put(\"jib.dockerClient.executable\", \"test-exec\");\n    assertThat(testPluginConfiguration.getDockerClientExecutable())\n        .isEqualTo(Paths.get(\"test-exec\"));\n    sessionUserProperties.put(\"jib.dockerClient.environment\", \"env1=val1,env2=val2\");\n    assertThat(testPluginConfiguration.getDockerClientEnvironment())\n        .containsExactly(\"env1\", \"val1\", \"env2\", \"val2\")\n        .inOrder();\n  }\n\n  @Test\n  public void testUserPropertiesWithInvalidPlatform() {\n    sessionUserProperties.put(\"jib.from.platforms\", \"linux /amd64\");\n    assertThrows(IllegalArgumentException.class, testPluginConfiguration::getPlatforms);\n  }\n\n  @Test\n  public void testUserPropertiesExtraDirectories() {\n    sessionUserProperties.put(\"jib.extraDirectories.paths\", \"custom-jib\");\n    assertThat(testPluginConfiguration.getExtraDirectories()).hasSize(1);\n    assertThat(testPluginConfiguration.getExtraDirectories().get(0).getFrom())\n        .isEqualTo(Paths.get(\"custom-jib\"));\n    assertThat(testPluginConfiguration.getExtraDirectories().get(0).getInto()).isEqualTo(\"/\");\n    sessionUserProperties.put(\n        \"jib.extraDirectories.permissions\", \"/test/file1=123,/another/file=456\");\n    List<PermissionConfiguration> permissions =\n        testPluginConfiguration.getExtraDirectoryPermissions();\n    assertThat(permissions.get(0).getFile()).hasValue(\"/test/file1\");\n    assertThat(permissions.get(0).getMode()).hasValue(\"123\");\n    assertThat(permissions.get(1).getFile()).hasValue(\"/another/file\");\n    assertThat(permissions.get(1).getMode()).hasValue(\"456\");\n  }\n\n  @Test\n  public void testUserPropertiesOutputPaths() {\n    // Absolute paths\n    sessionUserProperties.put(\"jib.outputPaths.digest\", \"/digest/path\");\n    assertThat(testPluginConfiguration.getDigestOutputPath()).isEqualTo(Paths.get(\"/digest/path\"));\n    sessionUserProperties.put(\"jib.outputPaths.imageId\", \"/id/path\");\n    assertThat(testPluginConfiguration.getImageIdOutputPath()).isEqualTo(Paths.get(\"/id/path\"));\n    sessionUserProperties.put(\"jib.outputPaths.tar\", \"/tar/path\");\n    assertThat(testPluginConfiguration.getTarOutputPath()).isEqualTo(Paths.get(\"/tar/path\"));\n    // Relative paths\n    sessionUserProperties.put(\"jib.outputPaths.digest\", \"digest/path\");\n    assertThat(testPluginConfiguration.getDigestOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/digest/path\"));\n    sessionUserProperties.put(\"jib.outputPaths.imageId\", \"id/path\");\n    assertThat(testPluginConfiguration.getImageIdOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/id/path\"));\n    sessionUserProperties.put(\"jib.outputPaths.imageJson\", \"json/path\");\n    assertThat(testPluginConfiguration.getImageJsonOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/json/path\"));\n    sessionUserProperties.put(\"jib.outputPaths.tar\", \"tar/path\");\n    assertThat(testPluginConfiguration.getTarOutputPath())\n        .isEqualTo(Paths.get(\"/repository/project/tar/path\"));\n  }\n\n  @Test\n  public void testPropertySourcePrecedence() {\n    sessionUserProperties.put(\"jib.to.image\", \"to:user\");\n    project.getProperties().setProperty(\"jib.to.image\", \"to:project\");\n    sessionSystemProperties.put(\"jib.to.image\", \"to:system\");\n    assertThat(testPluginConfiguration.getTargetImage()).isEqualTo(\"to:user\");\n\n    project.getProperties().setProperty(\"jib.from.image\", \"from:project\");\n    sessionSystemProperties.put(\"jib.to.image\", \"from:system\");\n    assertThat(testPluginConfiguration.getBaseImage()).isEqualTo(\"from:project\");\n\n    sessionUserProperties.put(\"jib.outputPaths.tar\", \"/tar/user\");\n    sessionSystemProperties.put(\"jib.outputPaths.tar\", \"/tar/system\");\n    assertThat(testPluginConfiguration.getTarOutputPath()).isEqualTo(Paths.get(\"/tar/user\"));\n\n    sessionUserProperties.put(\"jib.outputPaths.imageId\", \"/image/user\");\n    project.getProperties().setProperty(\"jib.outputPaths.imageId\", \"/image/project\");\n    assertThat(testPluginConfiguration.getImageIdOutputPath()).isEqualTo(Paths.get(\"/image/user\"));\n  }\n\n  @Test\n  public void testEmptyOrNullTags() {\n    // https://github.com/GoogleContainerTools/jib/issues/1534\n    // Maven turns empty tags into null entries, and its possible to have empty tags in jib.to.tags\n    sessionSystemProperties.put(\"jib.to.tags\", \"a,,b\");\n    Exception ex =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> testPluginConfiguration.getTargetImageAdditionalTags());\n    assertThat(ex.getMessage()).isEqualTo(\"jib.to.tags has empty tag\");\n  }\n\n  @Test\n  public void testIsContainerizable_noProperty() {\n    Properties projectProperties = project.getProperties();\n\n    projectProperties.remove(\"jib.containerize\");\n    assertThat(testPluginConfiguration.isContainerizable()).isTrue();\n\n    projectProperties.setProperty(\"jib.containerize\", \"\");\n    assertThat(testPluginConfiguration.isContainerizable()).isTrue();\n  }\n\n  @Test\n  public void testIsContainerizable_artifactId() {\n    project.setGroupId(\"group\");\n    project.setArtifactId(\"artifact\");\n\n    Properties projectProperties = project.getProperties();\n    projectProperties.setProperty(\"jib.containerize\", \":artifact\");\n    assertThat(testPluginConfiguration.isContainerizable()).isTrue();\n\n    projectProperties.setProperty(\"jib.containerize\", \":artifact2\");\n    assertThat(testPluginConfiguration.isContainerizable()).isFalse();\n  }\n\n  @Test\n  public void testIsContainerizable_groupAndArtifactId() {\n    project.setGroupId(\"group\");\n    project.setArtifactId(\"artifact\");\n\n    Properties projectProperties = project.getProperties();\n    projectProperties.setProperty(\"jib.containerize\", \"group:artifact\");\n    assertThat(testPluginConfiguration.isContainerizable()).isTrue();\n\n    projectProperties.setProperty(\"jib.containerize\", \"group:artifact2\");\n    assertThat(testPluginConfiguration.isContainerizable()).isFalse();\n  }\n\n  @Test\n  public void testIsContainerizable_directory() {\n    project.setGroupId(\"group\");\n    project.setArtifactId(\"artifact\");\n\n    Properties projectProperties = project.getProperties();\n    projectProperties.setProperty(\"jib.containerize\", \"project\");\n    assertThat(testPluginConfiguration.isContainerizable()).isTrue();\n\n    projectProperties.setProperty(\"jib.containerize\", \"project2\");\n    assertThat(testPluginConfiguration.isContainerizable()).isFalse();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenProjectPropertiesExtensionTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension;\nimport com.google.cloud.tools.jib.maven.extension.MavenData;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger.LogLevel;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.collect.ImmutableMap;\nimport java.io.FileNotFoundException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport org.apache.maven.execution.MavenExecutionRequest;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.plugin.descriptor.PluginDescriptor;\nimport org.apache.maven.plugin.logging.Log;\nimport org.apache.maven.project.MavenProject;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Plugin extension test for {@link MavenProjectProperties}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class MavenProjectPropertiesExtensionTest {\n\n  // Interface to conveniently provide the main extension body using lambda.\n  @FunctionalInterface\n  private interface MainExtensionBody<T> {\n\n    ContainerBuildPlan extendContainerBuildPlan(\n        ContainerBuildPlan buildPlan,\n        Map<String, String> properties,\n        Optional<T> extraConfig,\n        MavenData mavenData,\n        ExtensionLogger logger)\n        throws JibPluginExtensionException;\n  }\n\n  private static class BaseExtension<T> implements JibMavenPluginExtension<T> {\n\n    private final MainExtensionBody<T> extensionBody;\n    private final Class<T> extraConfigType;\n\n    private BaseExtension(MainExtensionBody<T> extensionBody, Class<T> extraConfigType) {\n      this.extensionBody = extensionBody;\n      this.extraConfigType = extraConfigType;\n    }\n\n    @Override\n    public Optional<Class<T>> getExtraConfigType() {\n      return Optional.ofNullable(extraConfigType);\n    }\n\n    @Override\n    public ContainerBuildPlan extendContainerBuildPlan(\n        ContainerBuildPlan buildPlan,\n        Map<String, String> properties,\n        Optional<T> extraConfig,\n        MavenData mavenData,\n        ExtensionLogger logger)\n        throws JibPluginExtensionException {\n      return extensionBody.extendContainerBuildPlan(\n          buildPlan, properties, extraConfig, mavenData, logger);\n    }\n  }\n\n  private static class FooExtension extends BaseExtension<ExtensionDefinedFooConfig> {\n\n    private FooExtension(MainExtensionBody<ExtensionDefinedFooConfig> extensionBody) {\n      super(extensionBody, ExtensionDefinedFooConfig.class);\n    }\n  }\n\n  private static class BarExtension extends BaseExtension<ExtensionDefinedBarConfig> {\n\n    private BarExtension(MainExtensionBody<ExtensionDefinedBarConfig> extensionBody) {\n      super(extensionBody, ExtensionDefinedBarConfig.class);\n    }\n  }\n\n  private static class BaseExtensionConfig<T> implements ExtensionConfiguration {\n\n    private final String extensionClass;\n    private final Map<String, String> properties;\n    private final T extraConfig;\n\n    private BaseExtensionConfig(\n        String extensionClass, Map<String, String> properties, T extraConfig) {\n      this.extensionClass = extensionClass;\n      this.properties = properties;\n      this.extraConfig = extraConfig;\n    }\n\n    @Override\n    public Map<String, String> getProperties() {\n      return properties;\n    }\n\n    @Override\n    public String getExtensionClass() {\n      return extensionClass;\n    }\n\n    @Override\n    public Optional<Object> getExtraConfiguration() {\n      return Optional.ofNullable(extraConfig);\n    }\n  }\n\n  private static class FooExtensionConfig extends BaseExtensionConfig<ExtensionDefinedFooConfig> {\n\n    private FooExtensionConfig() {\n      super(FooExtension.class.getName(), Collections.emptyMap(), null);\n    }\n\n    private FooExtensionConfig(Map<String, String> properties) {\n      super(FooExtension.class.getName(), properties, null);\n    }\n\n    private FooExtensionConfig(ExtensionDefinedFooConfig extraConfig) {\n      super(FooExtension.class.getName(), Collections.emptyMap(), extraConfig);\n    }\n  }\n\n  private static class BarExtensionConfig extends BaseExtensionConfig<ExtensionDefinedBarConfig> {\n\n    private BarExtensionConfig() {\n      super(BarExtension.class.getName(), Collections.emptyMap(), null);\n    }\n\n    private BarExtensionConfig(ExtensionDefinedBarConfig extraConfig) {\n      super(BarExtension.class.getName(), Collections.emptyMap(), extraConfig);\n    }\n  }\n\n  // Not to be confused with Jib's plugin extension config. This class is for an extension-defined\n  // config specific to a third-party extension.\n  private static class ExtensionDefinedFooConfig {\n\n    private String fooParam;\n\n    private ExtensionDefinedFooConfig(String fooParam) {\n      this.fooParam = fooParam;\n    }\n  }\n\n  private static class ExtensionDefinedBarConfig {\n\n    private String barParam;\n\n    private ExtensionDefinedBarConfig(String barParam) {\n      this.barParam = barParam;\n    }\n  }\n\n  @Mock private PluginDescriptor mockJibPluginDescriptor;\n  @Mock private MavenProject mockMavenProject;\n  @Mock private MavenSession mockMavenSession;\n  @Mock private MavenExecutionRequest mockMavenRequest;\n  @Mock private Log mockLog;\n  @Mock private TempDirectoryProvider mockTempDirectoryProvider;\n\n  private List<JibMavenPluginExtension<?>> loadedExtensions = Collections.emptyList();\n  private final JibContainerBuilder containerBuilder = Jib.fromScratch();\n\n  private MavenProjectProperties mavenProjectProperties;\n\n  @Before\n  public void setUp() {\n    Mockito.when(mockLog.isDebugEnabled()).thenReturn(true);\n    Mockito.when(mockLog.isWarnEnabled()).thenReturn(true);\n    Mockito.when(mockLog.isErrorEnabled()).thenReturn(true);\n\n    Mockito.when(mockMavenSession.getRequest()).thenReturn(mockMavenRequest);\n    mavenProjectProperties =\n        new MavenProjectProperties(\n            mockJibPluginDescriptor,\n            mockMavenProject,\n            mockMavenSession,\n            mockLog,\n            mockTempDirectoryProvider,\n            Collections.emptyList(),\n            () -> loadedExtensions);\n  }\n\n  @Test\n  public void testRunPluginExtensions_noExtensionsConfigured() throws JibPluginExtensionException {\n    FooExtension extension =\n        new FooExtension((buildPlan, properties, extraConfig, mavenData, logger) -> buildPlan);\n    loadedExtensions = Arrays.asList(extension);\n\n    JibContainerBuilder extendedBuilder =\n        mavenProjectProperties.runPluginExtensions(Collections.emptyList(), containerBuilder);\n    Assert.assertSame(extendedBuilder, containerBuilder);\n\n    mavenProjectProperties.waitForLoggingThread();\n    Mockito.verify(mockLog).debug(\"No Jib plugin extensions configured to load\");\n  }\n\n  @Test\n  public void testRunPluginExtensions_configuredExtensionNotFound() {\n    try {\n      mavenProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(\n          \"extension configured but not discovered on Jib runtime classpath: com.google.cloud.\"\n              + \"tools.jib.maven.MavenProjectPropertiesExtensionTest$FooExtension\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions() throws JibPluginExtensionException {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              logger.log(LogLevel.ERROR, \"awesome error from my extension\");\n              return buildPlan.toBuilder().setUser(\"user from extension\").build();\n            });\n    loadedExtensions = Arrays.asList(extension);\n\n    JibContainerBuilder extendedBuilder =\n        mavenProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"user from extension\", extendedBuilder.toContainerBuildPlan().getUser());\n\n    mavenProjectProperties.waitForLoggingThread();\n    Mockito.verify(mockLog).error(\"awesome error from my extension\");\n    Mockito.verify(mockLog)\n        .info(\n            Mockito.startsWith(\n                \"Running extension: com.google.cloud.tools.jib.maven.MavenProjectProperties\"));\n  }\n\n  @Test\n  public void testRunPluginExtensions_exceptionFromExtension() {\n    FileNotFoundException fakeException = new FileNotFoundException();\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              throw new JibPluginExtensionException(\n                  FooExtension.class, \"exception from extension\", fakeException);\n            });\n    loadedExtensions = Arrays.asList(extension);\n\n    try {\n      mavenProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(\"exception from extension\", ex.getMessage());\n      Assert.assertSame(fakeException, ex.getCause());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_invalidBaseImageFromExtension() {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\" in*val+id\").build());\n    loadedExtensions = Arrays.asList(extension);\n\n    try {\n      mavenProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(\"invalid base image reference:  in*val+id\", ex.getMessage());\n      MatcherAssert.assertThat(\n          ex.getCause(), CoreMatchers.instanceOf(InvalidImageReferenceException.class));\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_extensionOrder() throws JibPluginExtensionException {\n    FooExtension fooExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\"foo\").build());\n    BarExtension barExtension =\n        new BarExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\"bar\").build());\n    loadedExtensions = Arrays.asList(fooExtension, barExtension);\n\n    JibContainerBuilder extendedBuilder1 =\n        mavenProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig(), new BarExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"bar\", extendedBuilder1.toContainerBuildPlan().getBaseImage());\n\n    JibContainerBuilder extendedBuilder2 =\n        mavenProjectProperties.runPluginExtensions(\n            Arrays.asList(new BarExtensionConfig(), new FooExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"foo\", extendedBuilder2.toContainerBuildPlan().getBaseImage());\n  }\n\n  @Test\n  public void testRunPluginExtensions_customProperties() throws JibPluginExtensionException {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) ->\n                buildPlan.toBuilder().setUser(properties.get(\"user\")).build());\n    loadedExtensions = Arrays.asList(extension);\n\n    JibContainerBuilder extendedBuilder =\n        mavenProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig(ImmutableMap.of(\"user\", \"65432\"))),\n            containerBuilder);\n    Assert.assertEquals(\"65432\", extendedBuilder.toContainerBuildPlan().getUser());\n  }\n\n  @Test\n  public void testRunPluginExtensions_extensionDefinedConfigurations_emptyConfig()\n      throws JibPluginExtensionException {\n    FooExtension fooExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(Optional.empty(), extraConfig);\n              return buildPlan;\n            });\n    BarExtension barExtension =\n        new BarExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(Optional.empty(), extraConfig);\n              return buildPlan;\n            });\n    loadedExtensions = Arrays.asList(fooExtension, barExtension);\n\n    mavenProjectProperties.runPluginExtensions(\n        Arrays.asList(new FooExtensionConfig(), new BarExtensionConfig()), containerBuilder);\n  }\n\n  @Test\n  public void testRunPluginExtensions_extensionDefinedConfigurations()\n      throws JibPluginExtensionException {\n    FooExtension fooExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(\"fooParamValue\", extraConfig.get().fooParam);\n              return buildPlan;\n            });\n    BarExtension barExtension =\n        new BarExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              Assert.assertEquals(\"barParamValue\", extraConfig.get().barParam);\n              return buildPlan;\n            });\n    loadedExtensions = Arrays.asList(fooExtension, barExtension);\n\n    mavenProjectProperties.runPluginExtensions(\n        Arrays.asList(\n            new FooExtensionConfig(new ExtensionDefinedFooConfig(\"fooParamValue\")),\n            new BarExtensionConfig(new ExtensionDefinedBarConfig(\"barParamValue\"))),\n        containerBuilder);\n  }\n\n  @Test\n  public void testRunPluginExtensions_wrongExtraConfigType() {\n    FooExtension extension =\n        new FooExtension((buildPlan, properties, extraConfig, mavenData, logger) -> buildPlan);\n    loadedExtensions = Arrays.asList(extension);\n\n    ExtensionConfiguration extensionConfig =\n        new BaseExtensionConfig<>(\n            FooExtension.class.getName(), Collections.emptyMap(), \"string <configuration>\");\n    try {\n      mavenProjectProperties.runPluginExtensions(Arrays.asList(extensionConfig), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(FooExtension.class, ex.getExtensionClass());\n      Assert.assertEquals(\n          \"extension-specific <configuration> for FooExtension is not of type com.google.cloud\"\n              + \".tools.jib.maven.MavenProjectPropertiesExtensionTest$ExtensionDefinedFooConfig \"\n              + \"but java.lang.String; specify the correct type with <pluginExtension>\"\n              + \"<configuration implementation=\\\"com.google.cloud.tools.jib.maven.\"\n              + \"MavenProjectPropertiesExtensionTest$ExtensionDefinedFooConfig\\\">\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_ignoreUnexpectedExtraConfig()\n      throws JibPluginExtensionException {\n    BaseExtension<Void> extension =\n        new BaseExtension<>(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> buildPlan, null);\n    loadedExtensions = Arrays.asList(extension);\n\n    ExtensionConfiguration extensionConfig =\n        new BaseExtensionConfig<>(\n            BaseExtension.class.getName(), Collections.emptyMap(), \"unwanted <configuration>\");\n    try {\n      mavenProjectProperties.runPluginExtensions(Arrays.asList(extensionConfig), containerBuilder);\n      Assert.fail();\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\n          \"extension BaseExtension does not expect extension-specific configuration; remove the \"\n              + \"inapplicable <pluginExtension><configuration> from pom.xml\",\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_runtimeExceptionFromExtension() {\n    FooExtension extension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              throw new IndexOutOfBoundsException(\"buggy extension\");\n            });\n    loadedExtensions = Arrays.asList(extension);\n\n    try {\n      mavenProjectProperties.runPluginExtensions(\n          Arrays.asList(new FooExtensionConfig()), containerBuilder);\n      Assert.fail();\n    } catch (JibPluginExtensionException ex) {\n      Assert.assertEquals(FooExtension.class, ex.getExtensionClass());\n      Assert.assertEquals(\"extension crashed: buggy extension\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testRunPluginExtensions_injected() throws JibPluginExtensionException {\n    FooExtension injectedExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              logger.log(LogLevel.ERROR, \"awesome error from my extension\");\n              return buildPlan.toBuilder().setUser(\"user from extension\").build();\n            });\n\n    mavenProjectProperties =\n        new MavenProjectProperties(\n            mockJibPluginDescriptor,\n            mockMavenProject,\n            mockMavenSession,\n            mockLog,\n            mockTempDirectoryProvider,\n            Arrays.asList(injectedExtension),\n            () -> Collections.emptyList());\n\n    JibContainerBuilder extendedBuilder =\n        mavenProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\"user from extension\", extendedBuilder.toContainerBuildPlan().getUser());\n\n    mavenProjectProperties.waitForLoggingThread();\n    Mockito.verify(mockLog).error(\"awesome error from my extension\");\n    Mockito.verify(mockLog)\n        .info(\n            Mockito.startsWith(\n                \"Running extension: com.google.cloud.tools.jib.maven.MavenProjectProperties\"));\n  }\n\n  @Test\n  public void testRunPluginExtensions_preferInjectionOverServiceLoader()\n      throws JibPluginExtensionException {\n    FooExtension injectedExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) -> {\n              logger.log(LogLevel.ERROR, \"awesome error from my extension\");\n              return buildPlan.toBuilder().setUser(\"user from injected extension\").build();\n            });\n\n    FooExtension loadedExtension =\n        new FooExtension(\n            (buildPlan, properties, extraConfig, mavenData, logger) ->\n                buildPlan.toBuilder().setBaseImage(\"loadedExtBaseImage\").build());\n\n    mavenProjectProperties =\n        new MavenProjectProperties(\n            mockJibPluginDescriptor,\n            mockMavenProject,\n            mockMavenSession,\n            mockLog,\n            mockTempDirectoryProvider,\n            Arrays.asList(injectedExtension),\n            () -> Arrays.asList(loadedExtension));\n\n    JibContainerBuilder extendedBuilder =\n        mavenProjectProperties.runPluginExtensions(\n            Arrays.asList(new FooExtensionConfig()), containerBuilder);\n    Assert.assertEquals(\n        \"user from injected extension\", extendedBuilder.toContainerBuildPlan().getUser());\n    Assert.assertEquals(\"scratch\", extendedBuilder.toContainerBuildPlan().getBaseImage());\n\n    mavenProjectProperties.waitForLoggingThread();\n    Mockito.verify(mockLog).error(\"awesome error from my extension\");\n    Mockito.verify(mockLog)\n        .info(\n            Mockito.startsWith(\n                \"Running extension: com.google.cloud.tools.jib.maven.MavenProjectProperties\"));\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenProjectPropertiesTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;\nimport com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension;\nimport com.google.cloud.tools.jib.plugins.common.ContainerizingMode;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.Resources;\nimport com.google.common.truth.Correspondence;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.StringJoiner;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.apache.maven.artifact.Artifact;\nimport org.apache.maven.artifact.DefaultArtifact;\nimport org.apache.maven.execution.MavenExecutionRequest;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.model.Build;\nimport org.apache.maven.model.Plugin;\nimport org.apache.maven.model.PluginExecution;\nimport org.apache.maven.plugin.descriptor.PluginDescriptor;\nimport org.apache.maven.plugin.logging.Log;\nimport org.apache.maven.project.MavenProject;\nimport org.codehaus.plexus.archiver.zip.ZipEntry;\nimport org.codehaus.plexus.archiver.zip.ZipOutputStream;\nimport org.codehaus.plexus.util.xml.Xpp3Dom;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Test for {@link MavenProjectProperties}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class MavenProjectPropertiesTest {\n\n  private static final Correspondence<FileEntry, Path> SOURCE_FILE_OF =\n      Correspondence.transforming(FileEntry::getSourceFile, \"has sourceFile of\");\n  private static final Correspondence<FileEntry, String> EXTRACTION_PATH_OF =\n      Correspondence.transforming(\n          entry -> entry.getExtractionPath().toString(), \"has extractionPath of\");\n\n  private static final Instant EPOCH_PLUS_32 = Instant.ofEpochSecond(32);\n\n  /** Helper for reading back layers in a {@link BuildContext}. */\n  private static class ContainerBuilderLayers {\n\n    @Nullable private final FileEntriesLayer resourcesLayer;\n    @Nullable private final FileEntriesLayer classesLayer;\n    @Nullable private final FileEntriesLayer dependenciesLayer;\n    @Nullable private final FileEntriesLayer snapshotsLayer;\n    @Nullable private final FileEntriesLayer extraFilesLayer;\n\n    private ContainerBuilderLayers(BuildContext buildContext) {\n      resourcesLayer = getLayerByName(buildContext, LayerType.RESOURCES.getName());\n      classesLayer = getLayerByName(buildContext, LayerType.CLASSES.getName());\n      dependenciesLayer = getLayerByName(buildContext, LayerType.DEPENDENCIES.getName());\n      snapshotsLayer = getLayerByName(buildContext, LayerType.SNAPSHOT_DEPENDENCIES.getName());\n      extraFilesLayer = getLayerByName(buildContext, LayerType.EXTRA_FILES.getName());\n    }\n\n    @Nullable\n    private static FileEntriesLayer getLayerByName(BuildContext buildContext, String name) {\n      List<FileEntriesLayer> layers = buildContext.getLayerConfigurations();\n      return layers.stream().filter(layer -> layer.getName().equals(name)).findFirst().orElse(null);\n    }\n  }\n\n  private static Path getResource(String path) throws URISyntaxException {\n    return Paths.get(Resources.getResource(path).toURI());\n  }\n\n  private static Path zipUpDirectory(Path sourceRoot, Path targetZip) throws IOException {\n    try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(targetZip))) {\n      for (Path source : new DirectoryWalker(sourceRoot).filterRoot().walk()) {\n\n        StringJoiner pathJoiner = new StringJoiner(\"/\", \"\", \"\");\n        sourceRoot.relativize(source).forEach(element -> pathJoiner.add(element.toString()));\n        String zipEntryPath =\n            Files.isDirectory(source) ? pathJoiner.toString() + '/' : pathJoiner.toString();\n\n        ZipEntry entry = new ZipEntry(zipEntryPath);\n        zipOut.putNextEntry(entry);\n        if (!Files.isDirectory(source)) {\n          try (InputStream in = Files.newInputStream(source)) {\n            ByteStreams.copy(in, zipOut);\n          }\n        }\n        zipOut.closeEntry();\n      }\n    }\n    return targetZip;\n  }\n\n  private static Artifact newArtifact(Path sourceJar) {\n    Artifact artifact = mock(Artifact.class);\n    when(artifact.getFile()).thenReturn(sourceJar.toFile());\n    return artifact;\n  }\n\n  private static Artifact newArtifact(String group, String artifactId, String version) {\n    Artifact artifact = new DefaultArtifact(group, artifactId, version, null, \"jar\", \"\", null);\n    artifact.setFile(new File(\"/tmp/\" + group + artifactId + version));\n    return artifact;\n  }\n\n  private static Xpp3Dom newXpp3Dom(String name, String value) {\n    Xpp3Dom node = new Xpp3Dom(name);\n    node.setValue(value);\n    return node;\n  }\n\n  private static Xpp3Dom addXpp3DomChild(Xpp3Dom parent, String name, String value) {\n    Xpp3Dom node = new Xpp3Dom(name);\n    node.setValue(value);\n    parent.addChild(node);\n    return node;\n  }\n\n  @Rule public final TestRepository testRepository = new TestRepository();\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private final Xpp3Dom pluginConfiguration = new Xpp3Dom(\"configuration\");\n\n  @Mock private Build mockBuild;\n  @Mock private PluginDescriptor mockJibPluginDescriptor;\n  @Mock private MavenProject mockMavenProject;\n  @Mock private MavenSession mockMavenSession;\n  @Mock private MavenExecutionRequest mockMavenRequest;\n  @Mock private Properties mockMavenProperties;\n  @Mock private Plugin mockPlugin;\n  @Mock private PluginExecution mockPluginExecution;\n  @Mock private Log mockLog;\n  @Mock private TempDirectoryProvider mockTempDirectoryProvider;\n  @Mock private Supplier<List<JibMavenPluginExtension<?>>> mockExtensionLoader;\n  @Mock private JavaContainerBuilder mockJavaContainerBuilder;\n\n  private MavenProjectProperties mavenProjectProperties;\n\n  @Before\n  public void setUp() throws IOException, URISyntaxException {\n    when(mockLog.isDebugEnabled()).thenReturn(true);\n    when(mockLog.isWarnEnabled()).thenReturn(true);\n    when(mockLog.isErrorEnabled()).thenReturn(true);\n\n    when(mockMavenSession.getRequest()).thenReturn(mockMavenRequest);\n    mavenProjectProperties =\n        new MavenProjectProperties(\n            mockJibPluginDescriptor,\n            mockMavenProject,\n            mockMavenSession,\n            mockLog,\n            mockTempDirectoryProvider,\n            Collections.emptyList(),\n            mockExtensionLoader);\n\n    Path outputPath = getResource(\"maven/application/output\");\n    Path dependenciesPath = getResource(\"maven/application/dependencies\");\n\n    when(mockMavenProject.getBuild()).thenReturn(mockBuild);\n    when(mockBuild.getOutputDirectory()).thenReturn(outputPath.toString());\n\n    Set<Artifact> artifacts =\n        ImmutableSet.of(\n            newArtifact(dependenciesPath.resolve(\"library.jarC.jar\")),\n            newArtifact(dependenciesPath.resolve(\"libraryB.jar\")),\n            newArtifact(dependenciesPath.resolve(\"libraryA.jar\")),\n            newArtifact(dependenciesPath.resolve(\"more/dependency-1.0.0.jar\")),\n            newArtifact(dependenciesPath.resolve(\"another/one/dependency-1.0.0.jar\")),\n            // Maven reads and populates \"Artifacts\" with its own processing, so read some from a\n            // repository\n            testRepository.findArtifact(\"com.test\", \"dependency\", \"1.0.0\"),\n            testRepository.findArtifact(\"com.test\", \"dependencyX\", \"1.0.0-SNAPSHOT\"));\n    when(mockMavenProject.getArtifacts()).thenReturn(artifacts);\n\n    Path emptyDirectory =\n        getResource(\"maven/webapp\").resolve(\"final-name/WEB-INF/classes/empty_dir\");\n    Files.createDirectories(emptyDirectory);\n\n    when(mockMavenProject.getProperties()).thenReturn(mockMavenProperties);\n  }\n\n  @Test\n  public void testGetMainClassFromJar_success() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getConfiguration()).thenReturn(pluginConfiguration);\n    Xpp3Dom archive = new Xpp3Dom(\"archive\");\n    Xpp3Dom manifest = new Xpp3Dom(\"manifest\");\n    pluginConfiguration.addChild(archive);\n    archive.addChild(manifest);\n    manifest.addChild(newXpp3Dom(\"mainClass\", \"some.main.class\"));\n\n    assertThat(mavenProjectProperties.getMainClassFromJarPlugin()).isEqualTo(\"some.main.class\");\n  }\n\n  @Test\n  public void testGetMainClassFromJar_missingMainClass() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getConfiguration()).thenReturn(pluginConfiguration);\n    Xpp3Dom archive = new Xpp3Dom(\"archive\");\n    archive.addChild(new Xpp3Dom(\"manifest\"));\n    pluginConfiguration.addChild(archive);\n\n    assertThat(mavenProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testGetMainClassFromJar_missingManifest() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getConfiguration()).thenReturn(pluginConfiguration);\n    pluginConfiguration.addChild(new Xpp3Dom(\"archive\"));\n\n    assertThat(mavenProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testGetMainClassFromJar_missingArchive() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getConfiguration()).thenReturn(pluginConfiguration);\n\n    assertThat(mavenProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testGetMainClassFromJar_missingConfiguration() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n\n    assertThat(mavenProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testGetMainClassFromJar_missingPlugin() {\n    assertThat(mavenProjectProperties.getMainClassFromJarPlugin()).isNull();\n  }\n\n  @Test\n  public void testIsWarProject() {\n    assertThat(mavenProjectProperties.isWarProject()).isFalse();\n  }\n\n  @Test\n  public void testGetVersionFromString() {\n    assertThat(MavenProjectProperties.getVersionFromString(\"1.8\")).isEqualTo(8);\n    assertThat(MavenProjectProperties.getVersionFromString(\"1.8.0_123\")).isEqualTo(8);\n    assertThat(MavenProjectProperties.getVersionFromString(\"11\")).isEqualTo(11);\n    assertThat(MavenProjectProperties.getVersionFromString(\"11.0.1\")).isEqualTo(11);\n\n    assertThat(MavenProjectProperties.getVersionFromString(\"asdfasdf\")).isEqualTo(0);\n    assertThat(MavenProjectProperties.getVersionFromString(\"\")).isEqualTo(0);\n    assertThat(MavenProjectProperties.getVersionFromString(\"11abc\")).isEqualTo(0);\n    assertThat(MavenProjectProperties.getVersionFromString(\"1.abc\")).isEqualTo(0);\n  }\n\n  @Test\n  public void testGetMajorJavaVersion_undefinedDefaultsTo6() {\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(6);\n  }\n\n  @Test\n  public void testGetMajorJavaVersion_targetProperty() {\n    when(mockMavenProperties.getProperty(\"maven.compiler.target\")).thenReturn(\"1.8\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(8);\n\n    when(mockMavenProperties.getProperty(\"maven.compiler.target\")).thenReturn(\"1.7\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(7);\n\n    when(mockMavenProperties.getProperty(\"maven.compiler.target\")).thenReturn(\"11\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(11);\n  }\n\n  @Test\n  public void testValidateBaseImageVersion_releaseProperty() {\n    when(mockMavenProperties.getProperty(\"maven.compiler.release\")).thenReturn(\"1.8\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(8);\n\n    when(mockMavenProperties.getProperty(\"maven.compiler.release\")).thenReturn(\"1.7\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(7);\n\n    when(mockMavenProperties.getProperty(\"maven.compiler.release\")).thenReturn(\"9\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(9);\n  }\n\n  @Test\n  public void testValidateBaseImageVersion_compilerPluginTarget() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-compiler-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getConfiguration()).thenReturn(pluginConfiguration);\n    Xpp3Dom compilerTarget = new Xpp3Dom(\"target\");\n    pluginConfiguration.addChild(compilerTarget);\n\n    compilerTarget.setValue(\"1.8\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(8);\n\n    compilerTarget.setValue(\"1.6\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(6);\n\n    compilerTarget.setValue(\"13\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(13);\n  }\n\n  @Test\n  public void testValidateBaseImageVersion_compilerPluginRelease() {\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-compiler-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getConfiguration()).thenReturn(pluginConfiguration);\n    Xpp3Dom compilerRelease = new Xpp3Dom(\"release\");\n    pluginConfiguration.addChild(compilerRelease);\n\n    compilerRelease.setValue(\"1.8\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(8);\n\n    compilerRelease.setValue(\"10\");\n    assertThat(mavenProjectProperties.getMajorJavaVersion()).isEqualTo(10);\n  }\n\n  @Test\n  public void isProgressFooterEnabled() {\n    when(mockMavenRequest.isInteractiveMode()).thenReturn(false);\n    assertThat(MavenProjectProperties.isProgressFooterEnabled(mockMavenSession)).isFalse();\n  }\n\n  @Test\n  public void testCreateContainerBuilder_correctFiles()\n      throws URISyntaxException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    ContainerBuilderLayers layers = new ContainerBuilderLayers(setUpBuildContext());\n\n    Path dependenciesPath = getResource(\"maven/application/dependencies\");\n    Path applicationDirectory = getResource(\"maven/application\");\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            testRepository.artifactPathOnDisk(\"com.test\", \"dependency\", \"1.0.0\"),\n            dependenciesPath.resolve(\"more/dependency-1.0.0.jar\"),\n            dependenciesPath.resolve(\"another/one/dependency-1.0.0.jar\"),\n            dependenciesPath.resolve(\"libraryA.jar\"),\n            dependenciesPath.resolve(\"libraryB.jar\"),\n            dependenciesPath.resolve(\"library.jarC.jar\"));\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            testRepository.artifactPathOnDisk(\"com.test\", \"dependencyX\", \"1.0.0-SNAPSHOT\"));\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"output/directory\"),\n            applicationDirectory.resolve(\"output/directory/somefile\"),\n            applicationDirectory.resolve(\"output/package\"),\n            applicationDirectory.resolve(\"output/resourceA\"),\n            applicationDirectory.resolve(\"output/resourceB\"),\n            applicationDirectory.resolve(\"output/world\"));\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            applicationDirectory.resolve(\"output/HelloWorld.class\"),\n            applicationDirectory.resolve(\"output/directory\"),\n            applicationDirectory.resolve(\"output/package\"),\n            applicationDirectory.resolve(\"output/package/some.class\"),\n            applicationDirectory.resolve(\"output/some.class\"));\n\n    List<FileEntry> allFileEntries = new ArrayList<>();\n    allFileEntries.addAll(layers.dependenciesLayer.getEntries());\n    allFileEntries.addAll(layers.snapshotsLayer.getEntries());\n    allFileEntries.addAll(layers.resourcesLayer.getEntries());\n    allFileEntries.addAll(layers.classesLayer.getEntries());\n    Set<Instant> modificationTimes =\n        allFileEntries.stream().map(FileEntry::getModificationTime).collect(Collectors.toSet());\n    assertThat(modificationTimes).containsExactly(EPOCH_PLUS_32);\n  }\n\n  @Test\n  public void testCreateContainerBuilder_packagedMode()\n      throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException,\n          URISyntaxException {\n    Path jar = temporaryFolder.newFile(\"final-name.jar\").toPath();\n    when(mockBuild.getDirectory()).thenReturn(temporaryFolder.getRoot().toString());\n    when(mockBuild.getFinalName()).thenReturn(\"final-name\");\n\n    ContainerBuilderLayers layers =\n        new ContainerBuilderLayers(setUpBuildContext(ContainerizingMode.PACKAGED));\n\n    Path dependenciesPath = getResource(\"maven/application/dependencies\");\n    assertThat(layers.dependenciesLayer).isNotNull();\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            testRepository.artifactPathOnDisk(\"com.test\", \"dependency\", \"1.0.0\"),\n            dependenciesPath.resolve(\"more/dependency-1.0.0.jar\"),\n            dependenciesPath.resolve(\"another/one/dependency-1.0.0.jar\"),\n            dependenciesPath.resolve(\"libraryA.jar\"),\n            dependenciesPath.resolve(\"libraryB.jar\"),\n            dependenciesPath.resolve(\"library.jarC.jar\"));\n    assertThat(layers.snapshotsLayer).isNotNull();\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            testRepository.artifactPathOnDisk(\"com.test\", \"dependencyX\", \"1.0.0-SNAPSHOT\"));\n    assertThat(layers.extraFilesLayer).isNotNull();\n    assertThat(layers.extraFilesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(jar);\n    assertThat(layers.resourcesLayer).isNull();\n    assertThat(layers.classesLayer).isNull();\n\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/libs/dependency-1.0.0-200.jar\",\n            \"/my/app/libs/dependency-1.0.0-480.jar\",\n            \"/my/app/libs/dependency-1.0.0-770.jar\",\n            \"/my/app/libs/library.jarC.jar\",\n            \"/my/app/libs/libraryA.jar\",\n            \"/my/app/libs/libraryB.jar\");\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/libs/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(layers.extraFilesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/classpath/final-name.jar\");\n  }\n\n  @Test\n  public void testCreateContainerBuilder_war_correctSourceFilePaths()\n      throws URISyntaxException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    Path unzipTarget = setUpWar(getResource(\"maven/webapp/final-name\"));\n\n    ContainerBuilderLayers layers = new ContainerBuilderLayers(setUpBuildContext());\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(unzipTarget.resolve(\"WEB-INF/lib/dependency-1.0.0.jar\"));\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(unzipTarget.resolve(\"WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\"));\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            unzipTarget.resolve(\"META-INF\"),\n            unzipTarget.resolve(\"META-INF/context.xml\"),\n            unzipTarget.resolve(\"Test.jsp\"),\n            unzipTarget.resolve(\"WEB-INF\"),\n            unzipTarget.resolve(\"WEB-INF/classes\"),\n            unzipTarget.resolve(\"WEB-INF/classes/empty_dir\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package/test.properties\"),\n            unzipTarget.resolve(\"WEB-INF/lib\"),\n            unzipTarget.resolve(\"WEB-INF/web.xml\"));\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            unzipTarget.resolve(\"WEB-INF/classes/HelloWorld.class\"),\n            unzipTarget.resolve(\"WEB-INF/classes/empty_dir\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package\"),\n            unzipTarget.resolve(\"WEB-INF/classes/package/Other.class\"));\n  }\n\n  @Test\n  public void testCreateContainerBuilder_war_correctExtractionPaths()\n      throws URISyntaxException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    setUpWar(getResource(\"maven/webapp/final-name\"));\n\n    ContainerBuilderLayers layers = new ContainerBuilderLayers(setUpBuildContext());\n    assertThat(layers.dependenciesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependency-1.0.0.jar\");\n    assertThat(layers.snapshotsLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(layers.resourcesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/META-INF\",\n            \"/my/app/META-INF/context.xml\",\n            \"/my/app/Test.jsp\",\n            \"/my/app/WEB-INF\",\n            \"/my/app/WEB-INF/classes\",\n            \"/my/app/WEB-INF/classes/empty_dir\",\n            \"/my/app/WEB-INF/classes/package\",\n            \"/my/app/WEB-INF/classes/package/test.properties\",\n            \"/my/app/WEB-INF/lib\",\n            \"/my/app/WEB-INF/web.xml\");\n    assertThat(layers.classesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/WEB-INF/classes/HelloWorld.class\",\n            \"/my/app/WEB-INF/classes/empty_dir\",\n            \"/my/app/WEB-INF/classes/package\",\n            \"/my/app/WEB-INF/classes/package/Other.class\");\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noErrorIfWebInfDoesNotExist()\n      throws IOException, InvalidImageReferenceException {\n    setUpWar(temporaryFolder.newFolder(\"final-name\").toPath());\n\n    assertThat(\n            mavenProjectProperties.createJibContainerBuilder(\n                JavaContainerBuilder.from(\"ignored\"), ContainerizingMode.EXPLODED))\n        .isNotNull();\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noErrorIfWebInfLibDoesNotExist()\n      throws IOException, InvalidImageReferenceException {\n    temporaryFolder.newFolder(\"final-name\", \"WEB-INF\", \"classes\");\n    setUpWar(temporaryFolder.getRoot().toPath());\n\n    assertThat(\n            mavenProjectProperties.createJibContainerBuilder(\n                JavaContainerBuilder.from(\"ignored\"), ContainerizingMode.EXPLODED))\n        .isNotNull();\n  }\n\n  @Test\n  public void testCreateContainerBuilder_exceptionMessageHasPackageSuggestionIfProjectIsWar()\n      throws IOException {\n    String expectedMessage =\n        \"Obtaining project build output files failed; make sure you have \"\n            + \"packaged your project before trying to build the image. (Did you accidentally run \\\"mvn clean \"\n            + \"jib:build\\\" instead of \\\"mvn clean package jib:build\\\"?)\";\n\n    when(mockMavenProject.getPackaging()).thenReturn(\"war\");\n    when(mockTempDirectoryProvider.newDirectory()).thenThrow(IOException.class);\n\n    for (ContainerizingMode containerizingMode : ContainerizingMode.values()) {\n      IOException thrownException =\n          assertThrows(\n              IOException.class,\n              () ->\n                  mavenProjectProperties.createJibContainerBuilder(\n                      mockJavaContainerBuilder, containerizingMode));\n      assertThat(thrownException).hasMessageThat().isEqualTo(expectedMessage);\n    }\n  }\n\n  @Test\n  public void\n      testCreateContainerBuilder_exceptionMessageHasCompileSuggestionIfProjectIsExplodedAndNotWar()\n          throws IOException {\n    String expectedMessage =\n        \"Obtaining project build output files failed; make sure you have \"\n            + \"compiled your project before trying to build the image. (Did you accidentally run \\\"mvn clean \"\n            + \"jib:build\\\" instead of \\\"mvn clean compile jib:build\\\"?)\";\n\n    when(mockMavenProject.getPackaging()).thenReturn(\"jar\");\n    when(mockJavaContainerBuilder.addResources(any(), any())).thenThrow(IOException.class);\n\n    IOException thrownException =\n        assertThrows(\n            IOException.class,\n            () ->\n                mavenProjectProperties.createJibContainerBuilder(\n                    mockJavaContainerBuilder, ContainerizingMode.EXPLODED));\n    assertThat(thrownException).hasMessageThat().isEqualTo(expectedMessage);\n  }\n\n  @Test\n  public void testCreateContainerBuilder_noErrorIfWebInfClassesDoesNotExist()\n      throws IOException, InvalidImageReferenceException {\n    temporaryFolder.newFolder(\"final-name\", \"WEB-INF\", \"lib\");\n    setUpWar(temporaryFolder.getRoot().toPath());\n\n    assertThat(\n            mavenProjectProperties.createJibContainerBuilder(\n                JavaContainerBuilder.from(\"ignored\"), ContainerizingMode.EXPLODED))\n        .isNotNull();\n  }\n\n  @Test\n  public void testIsWarProject_warPackagingIsWar() {\n    when(mockMavenProject.getPackaging()).thenReturn(\"war\");\n    assertThat(mavenProjectProperties.isWarProject()).isTrue();\n  }\n\n  @Test\n  public void testIsWarProject_gwtAppPackagingIsWar() {\n    when(mockMavenProject.getPackaging()).thenReturn(\"gwt-app\");\n    assertThat(mavenProjectProperties.isWarProject()).isTrue();\n  }\n\n  @Test\n  public void testIsWarProject_jarPackagingIsNotWar() {\n    when(mockMavenProject.getPackaging()).thenReturn(\"jar\");\n    assertThat(mavenProjectProperties.isWarProject()).isFalse();\n  }\n\n  @Test\n  public void testIsWarProject_gwtLibPackagingIsNotWar() {\n    when(mockMavenProject.getPackaging()).thenReturn(\"gwt-lib\");\n    assertThat(mavenProjectProperties.isWarProject()).isFalse();\n  }\n\n  @Test\n  public void testClassifyDependencies() {\n    Set<Artifact> artifacts =\n        ImmutableSet.of(\n            newArtifact(\"com.test\", \"dependencyA\", \"1.0\"),\n            newArtifact(\"com.test\", \"dependencyB\", \"4.0-SNAPSHOT\"),\n            newArtifact(\"com.test\", \"projectA\", \"1.0\"),\n            newArtifact(\"com.test\", \"dependencyC\", \"1.0-SNAPSHOT\"),\n            newArtifact(\"com.test\", \"dependencyD\", \"4.0\"),\n            newArtifact(\"com.test\", \"projectB\", \"1.0-SNAPSHOT\"),\n            newArtifact(\"com.test\", \"projectC\", \"3.0\"));\n\n    Set<Artifact> projectArtifacts =\n        ImmutableSet.of(\n            newArtifact(\"com.test\", \"projectA\", \"1.0\"),\n            newArtifact(\"com.test\", \"projectB\", \"1.0-SNAPSHOT\"),\n            newArtifact(\"com.test\", \"projectC\", \"3.0\"));\n\n    Map<LayerType, List<Path>> classified =\n        mavenProjectProperties.classifyDependencies(artifacts, projectArtifacts);\n\n    assertThat(classified.get(LayerType.DEPENDENCIES))\n        .containsExactly(\n            newArtifact(\"com.test\", \"dependencyA\", \"1.0\").getFile().toPath(),\n            newArtifact(\"com.test\", \"dependencyD\", \"4.0\").getFile().toPath());\n\n    assertThat(classified.get(LayerType.SNAPSHOT_DEPENDENCIES))\n        .containsExactly(\n            newArtifact(\"com.test\", \"dependencyB\", \"4.0-SNAPSHOT\").getFile().toPath(),\n            newArtifact(\"com.test\", \"dependencyC\", \"1.0-SNAPSHOT\").getFile().toPath());\n\n    assertThat(classified.get(LayerType.PROJECT_DEPENDENCIES))\n        .containsExactly(\n            newArtifact(\"com.test\", \"projectA\", \"1.0\").getFile().toPath(),\n            newArtifact(\"com.test\", \"projectB\", \"1.0-SNAPSHOT\").getFile().toPath(),\n            newArtifact(\"com.test\", \"projectC\", \"3.0\").getFile().toPath());\n  }\n\n  @Test\n  public void testGetProjectDependencies() {\n    MavenProject rootPomProject = mock(MavenProject.class);\n    MavenProject jibSubModule = mock(MavenProject.class);\n    MavenProject sharedLibSubModule = mock(MavenProject.class);\n    when(mockMavenSession.getProjects())\n        .thenReturn(Arrays.asList(rootPomProject, sharedLibSubModule, jibSubModule));\n\n    Artifact nullFileArtifact = mock(Artifact.class);\n    Artifact projectJar = newArtifact(\"com.test\", \"my-app\", \"1.0\");\n    Artifact sharedLibJar = newArtifact(\"com.test\", \"shared-lib\", \"1.0\");\n\n    when(rootPomProject.getArtifact()).thenReturn(nullFileArtifact);\n    when(jibSubModule.getArtifact()).thenReturn(projectJar);\n    when(sharedLibSubModule.getArtifact()).thenReturn(sharedLibJar);\n\n    when(mockMavenProject.getArtifact()).thenReturn(projectJar);\n\n    assertThat(mavenProjectProperties.getProjectDependencies()).containsExactly(sharedLibJar);\n  }\n\n  @Test\n  public void testGetChildValue_null() {\n    assertThat(MavenProjectProperties.getChildValue(null)).isEmpty();\n    assertThat(MavenProjectProperties.getChildValue(null, \"foo\", \"bar\")).isEmpty();\n  }\n\n  @Test\n  public void testGetChildValue_noPathGiven() {\n    Xpp3Dom root = newXpp3Dom(\"root\", \"value\");\n\n    assertThat(MavenProjectProperties.getChildValue(root)).isEqualTo(Optional.of(\"value\"));\n  }\n\n  @Test\n  public void testGetChildValue_noChild() {\n    Xpp3Dom root = newXpp3Dom(\"root\", \"value\");\n\n    assertThat(MavenProjectProperties.getChildValue(root, \"foo\")).isEmpty();\n    assertThat(MavenProjectProperties.getChildValue(root, \"foo\", \"bar\")).isEmpty();\n  }\n\n  @Test\n  public void testGetChildValue_childPathMatched() {\n    Xpp3Dom root = newXpp3Dom(\"root\", \"value\");\n    Xpp3Dom foo = addXpp3DomChild(root, \"foo\", \"foo\");\n    addXpp3DomChild(foo, \"bar\", \"bar\");\n\n    assertThat(MavenProjectProperties.getChildValue(root, \"foo\")).isEqualTo(Optional.of(\"foo\"));\n    assertThat(MavenProjectProperties.getChildValue(root, \"foo\", \"bar\"))\n        .isEqualTo(Optional.of(\"bar\"));\n    assertThat(MavenProjectProperties.getChildValue(foo, \"bar\")).isEqualTo(Optional.of(\"bar\"));\n  }\n\n  @Test\n  public void testGetChildValue_notFullyMatched() {\n    Xpp3Dom root = newXpp3Dom(\"root\", \"value\");\n    Xpp3Dom foo = addXpp3DomChild(root, \"foo\", \"foo\");\n\n    addXpp3DomChild(foo, \"bar\", \"bar\");\n    assertThat(MavenProjectProperties.getChildValue(root, \"baz\")).isEmpty();\n    assertThat(MavenProjectProperties.getChildValue(root, \"foo\", \"baz\")).isEmpty();\n  }\n\n  @Test\n  public void testGetChildValue_nullValue() {\n    Xpp3Dom root = new Xpp3Dom(\"root\");\n    addXpp3DomChild(root, \"foo\", null);\n\n    assertThat(MavenProjectProperties.getChildValue(root)).isEmpty();\n    assertThat(MavenProjectProperties.getChildValue(root, \"foo\")).isEmpty();\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_pluginNotApplied() {\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration()).isEmpty();\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_noConfigurationBlock() {\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getGoals()).thenReturn(Arrays.asList(\"repackage\"));\n    when(mockPluginExecution.getConfiguration()).thenReturn(null);\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration())\n        .isEqualTo(Optional.of(new Xpp3Dom(\"configuration\")));\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_noExecutions() {\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Collections.emptyList());\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration()).isEmpty();\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_noRepackageGoal() {\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getGoals()).thenReturn(Arrays.asList(\"goal\", \"foo\", \"bar\"));\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration()).isEmpty();\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_repackageGoal() {\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getGoals()).thenReturn(Arrays.asList(\"goal\", \"repackage\"));\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration())\n        .isEqualTo(Optional.of(pluginConfiguration));\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_skipped() {\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getGoals()).thenReturn(Arrays.asList(\"repackage\"));\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"skip\", \"true\");\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration()).isEmpty();\n  }\n\n  @Test\n  public void testGetSpringBootRepackageConfiguration_skipNotTrue() {\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getGoals()).thenReturn(Arrays.asList(\"repackage\"));\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"skip\", null);\n    assertThat(mavenProjectProperties.getSpringBootRepackageConfiguration())\n        .isEqualTo(Optional.of(pluginConfiguration));\n  }\n\n  @Test\n  public void testGetJarArtifact() throws IOException {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(Paths.get(\"/foo/bar/helloworld-1.jar\"));\n  }\n\n  @Test\n  public void testGetJarArtifact_outputDirectoryFromJarPlugin() throws IOException {\n    when(mockMavenProject.getBasedir()).thenReturn(new File(\"/should/ignore\"));\n    when(mockBuild.getDirectory()).thenReturn(\"/should/ignore\");\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", Paths.get(\"/jar/out\").toString());\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(Paths.get(\"/jar/out/helloworld-1.jar\"));\n  }\n\n  @Test\n  public void testGetJarArtifact_relativeOutputDirectoryFromJarPlugin() throws IOException {\n    when(mockMavenProject.getBasedir()).thenReturn(new File(\"/base/dir\"));\n    when(mockBuild.getDirectory()).thenReturn(temporaryFolder.getRoot().toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", Paths.get(\"relative\").toString());\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(Paths.get(\"/base/dir/relative/helloworld-1.jar\"));\n  }\n\n  @Test\n  public void testGetJarArtifact_classifier() throws IOException {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"classifier\", \"a-class\");\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(Paths.get(\"/foo/bar/helloworld-1-a-class.jar\"));\n  }\n\n  @Test\n  public void testGetJarArtifact_executionIdNotMatched() throws IOException {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"no-id-match\");\n    Mockito.lenient().when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", \"/should/ignore\");\n    addXpp3DomChild(pluginConfiguration, \"classifier\", \"a-class\");\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(Paths.get(\"/foo/bar/helloworld-1.jar\"));\n  }\n\n  @Test\n  public void testGetJarArtifact_originalJarCopiedIfSpringBoot() throws IOException {\n    temporaryFolder.newFile(\"helloworld-1.jar.original\");\n    when(mockBuild.getDirectory()).thenReturn(temporaryFolder.getRoot().toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    setUpSpringBootFatJar();\n    Path tempDirectory = temporaryFolder.newFolder(\"tmp\").toPath();\n    when(mockTempDirectoryProvider.newDirectory()).thenReturn(tempDirectory);\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(tempDirectory.resolve(\"helloworld-1.original.jar\"));\n\n    mavenProjectProperties.waitForLoggingThread();\n    verify(mockLog).info(\"Spring Boot repackaging (fat JAR) detected; using the original JAR\");\n  }\n\n  @Test\n  public void testGetJarArtifact_originalJarIfSpringBoot_differentDirectories() throws IOException {\n    when(mockMavenProject.getBasedir()).thenReturn(new File(\"/should/ignore\"));\n    when(mockBuild.getDirectory()).thenReturn(\"/should/ignore\");\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", Paths.get(\"/jar/out\").toString());\n\n    setUpSpringBootFatJar();\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(Paths.get(\"/jar/out/helloworld-1.jar\"));\n\n    mavenProjectProperties.waitForLoggingThread();\n    verify(mockLog).info(\"Spring Boot repackaging (fat JAR) detected; using the original JAR\");\n  }\n\n  @Test\n  public void testGetJarArtifact_originalJarIfSpringBoot_differentFinalNames() throws IOException {\n    Path buildDirectory = temporaryFolder.newFolder(\"target\").toPath();\n    Files.createFile(buildDirectory.resolve(\"helloworld-1.jar\"));\n    when(mockMavenProject.getBasedir()).thenReturn(temporaryFolder.getRoot());\n    when(mockBuild.getDirectory()).thenReturn(buildDirectory.toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", \"target\");\n\n    Xpp3Dom bootPluginConfiguration = setUpSpringBootFatJar();\n    addXpp3DomChild(bootPluginConfiguration, \"finalName\", \"boot-helloworld-1\");\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(buildDirectory.resolve(\"helloworld-1.jar\"));\n\n    mavenProjectProperties.waitForLoggingThread();\n    verify(mockLog).info(\"Spring Boot repackaging (fat JAR) detected; using the original JAR\");\n  }\n\n  @Test\n  public void testGetJarArtifact_originalJarIfSpringBoot_differentClassifier() throws IOException {\n    Path buildDirectory = temporaryFolder.newFolder(\"target\").toPath();\n    Files.createFile(buildDirectory.resolve(\"helloworld-1.jar\"));\n    when(mockMavenProject.getBasedir()).thenReturn(temporaryFolder.getRoot());\n    when(mockBuild.getDirectory()).thenReturn(buildDirectory.toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", \"target\");\n\n    Xpp3Dom bootPluginConfiguration = setUpSpringBootFatJar();\n    addXpp3DomChild(bootPluginConfiguration, \"classifier\", \"boot-class\");\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(buildDirectory.resolve(\"helloworld-1.jar\"));\n\n    mavenProjectProperties.waitForLoggingThread();\n    verify(mockLog).info(\"Spring Boot repackaging (fat JAR) detected; using the original JAR\");\n  }\n\n  @Test\n  public void testGetJarArtifact_originalJarCopiedIfSpringBoot_sameDirectory() throws IOException {\n    Path buildDirectory = temporaryFolder.newFolder(\"target\").toPath();\n    Files.createFile(buildDirectory.resolve(\"helloworld-1.jar.original\"));\n    when(mockMavenProject.getBasedir()).thenReturn(temporaryFolder.getRoot());\n    when(mockBuild.getDirectory()).thenReturn(buildDirectory.toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-jar-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-jar\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"outputDirectory\", \"target\");\n\n    setUpSpringBootFatJar();\n    Path tempDirectory = temporaryFolder.newFolder(\"tmp\").toPath();\n    when(mockTempDirectoryProvider.newDirectory()).thenReturn(tempDirectory);\n\n    assertThat(mavenProjectProperties.getJarArtifact())\n        .isEqualTo(tempDirectory.resolve(\"helloworld-1.original.jar\"));\n\n    mavenProjectProperties.waitForLoggingThread();\n    verify(mockLog).info(\"Spring Boot repackaging (fat JAR) detected; using the original JAR\");\n  }\n\n  @Test\n  public void testGetWarArtifact() {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    assertThat(mavenProjectProperties.getWarArtifact())\n        .isEqualTo(Paths.get(\"/foo/bar/helloworld-1.war\"));\n  }\n\n  @Test\n  public void testGetWarArtifact_warNameProperty() {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-war-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-war\");\n    when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"warName\", \"baz\");\n\n    assertThat(mavenProjectProperties.getWarArtifact()).isEqualTo(Paths.get(\"/foo/bar/baz.war\"));\n  }\n\n  @Test\n  public void testGetWarArtifact_noWarNameProperty() {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-war-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"default-war\");\n    Mockito.lenient().when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n\n    assertThat(mavenProjectProperties.getWarArtifact())\n        .isEqualTo(Paths.get(\"/foo/bar/helloworld-1.war\"));\n  }\n\n  @Test\n  public void testGetWarArtifact_executionIdNotMatched() {\n    when(mockBuild.getDirectory()).thenReturn(Paths.get(\"/foo/bar\").toString());\n    when(mockBuild.getFinalName()).thenReturn(\"helloworld-1\");\n\n    when(mockMavenProject.getPlugin(\"org.apache.maven.plugins:maven-war-plugin\"))\n        .thenReturn(mockPlugin);\n    when(mockPlugin.getExecutions()).thenReturn(Arrays.asList(mockPluginExecution));\n    when(mockPluginExecution.getId()).thenReturn(\"no-id-match\");\n    Mockito.lenient().when(mockPluginExecution.getConfiguration()).thenReturn(pluginConfiguration);\n    addXpp3DomChild(pluginConfiguration, \"warName\", \"baz\");\n\n    assertThat(mavenProjectProperties.getWarArtifact())\n        .isEqualTo(Paths.get(\"/foo/bar/helloworld-1.war\"));\n  }\n\n  @Test\n  public void testGetDependencies() throws URISyntaxException {\n    assertThat(mavenProjectProperties.getDependencies())\n        .containsExactly(\n            getResource(\"maven/application/dependencies/library.jarC.jar\"),\n            getResource(\"maven/application/dependencies/libraryB.jar\"),\n            getResource(\"maven/application/dependencies/libraryA.jar\"),\n            getResource(\"maven/application/dependencies/more/dependency-1.0.0.jar\"),\n            getResource(\"maven/application/dependencies/another/one/dependency-1.0.0.jar\"),\n            testRepository.artifactPathOnDisk(\"com.test\", \"dependency\", \"1.0.0\"),\n            testRepository.artifactPathOnDisk(\"com.test\", \"dependencyX\", \"1.0.0-SNAPSHOT\"));\n  }\n\n  private BuildContext setUpBuildContext()\n      throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException {\n    return setUpBuildContext(ContainerizingMode.EXPLODED);\n  }\n\n  private BuildContext setUpBuildContext(ContainerizingMode containerizingMode)\n      throws InvalidImageReferenceException, IOException, CacheDirectoryCreationException {\n    JavaContainerBuilder javaContainerBuilder =\n        JavaContainerBuilder.from(RegistryImage.named(\"base\"))\n            .setAppRoot(AbsoluteUnixPath.get(\"/my/app\"))\n            .setModificationTimeProvider((ignored1, ignored2) -> EPOCH_PLUS_32);\n    JibContainerBuilder jibContainerBuilder =\n        mavenProjectProperties.createJibContainerBuilder(javaContainerBuilder, containerizingMode);\n    return JibContainerBuilderTestHelper.toBuildContext(\n        jibContainerBuilder, Containerizer.to(RegistryImage.named(\"to\")));\n  }\n\n  private Path setUpWar(Path explodedWar) throws IOException {\n    Path fakeMavenBuildDirectory = temporaryFolder.getRoot().toPath();\n    when(mockBuild.getDirectory()).thenReturn(fakeMavenBuildDirectory.toString());\n    when(mockBuild.getFinalName()).thenReturn(\"final-name\");\n    when(mockMavenProject.getPackaging()).thenReturn(\"war\");\n\n    zipUpDirectory(explodedWar, fakeMavenBuildDirectory.resolve(\"final-name.war\"));\n\n    // Make \"MavenProjectProperties\" use this folder to explode the WAR into.\n    Path unzipTarget = temporaryFolder.newFolder(\"exploded\").toPath();\n    when(mockTempDirectoryProvider.newDirectory()).thenReturn(unzipTarget);\n    return unzipTarget;\n  }\n\n  private Xpp3Dom setUpSpringBootFatJar() {\n    Xpp3Dom pluginConfiguration = new Xpp3Dom(\"configuration\");\n    PluginExecution execution = mock(PluginExecution.class);\n    Plugin plugin = mock(Plugin.class);\n    when(mockMavenProject.getPlugin(\"org.springframework.boot:spring-boot-maven-plugin\"))\n        .thenReturn(plugin);\n    when(plugin.getExecutions()).thenReturn(Arrays.asList(execution));\n    when(execution.getGoals()).thenReturn(Arrays.asList(\"repackage\"));\n    when(execution.getConfiguration()).thenReturn(pluginConfiguration);\n    return pluginConfiguration;\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.event.EventHandlers;\nimport com.google.cloud.tools.jib.maven.JibPluginConfiguration.FromAuthConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Sets;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Optional;\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.settings.Server;\nimport org.apache.maven.settings.Settings;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.mockito.Mockito;\n\n/** Test for {@link MavenRawConfiguration}. */\npublic class MavenRawConfigurationTest {\n\n  @Test\n  public void testGetters() {\n    JibPluginConfiguration jibPluginConfiguration = Mockito.mock(JibPluginConfiguration.class);\n    EventHandlers eventHandlers = Mockito.mock(EventHandlers.class);\n\n    Server server = Mockito.mock(Server.class);\n    Mockito.when(server.getUsername()).thenReturn(\"maven settings user\");\n    Mockito.when(server.getPassword()).thenReturn(\"maven settings password\");\n\n    Settings mavenSettings = Mockito.mock(Settings.class);\n    Mockito.when(mavenSettings.getServer(\"base registry\")).thenReturn(server);\n\n    MavenSession mavenSession = Mockito.mock(MavenSession.class);\n    Mockito.when(mavenSession.getSettings()).thenReturn(mavenSettings);\n\n    FromAuthConfiguration auth = Mockito.mock(FromAuthConfiguration.class);\n    Mockito.when(auth.getUsername()).thenReturn(\"user\");\n    Mockito.when(auth.getPassword()).thenReturn(\"password\");\n    Mockito.when(auth.getAuthDescriptor()).thenReturn(\"<from><auth>\");\n    Mockito.when(auth.getUsernameDescriptor()).thenReturn(\"<from><auth><username>\");\n    Mockito.when(auth.getPasswordDescriptor()).thenReturn(\"<from><auth><password>\");\n\n    Mockito.when(jibPluginConfiguration.getSession()).thenReturn(mavenSession);\n    Mockito.when(jibPluginConfiguration.getBaseImageAuth()).thenReturn(auth);\n\n    Mockito.when(jibPluginConfiguration.getAllowInsecureRegistries()).thenReturn(true);\n    Mockito.when(jibPluginConfiguration.getAppRoot()).thenReturn(\"/app/root\");\n    Mockito.when(jibPluginConfiguration.getArgs()).thenReturn(Arrays.asList(\"--log\", \"info\"));\n    Mockito.when(jibPluginConfiguration.getBaseImage()).thenReturn(\"openjdk:15\");\n\n    CredHelperConfiguration baseImageCredHelperConfig = Mockito.mock(CredHelperConfiguration.class);\n    Mockito.when(baseImageCredHelperConfig.getHelperName()).thenReturn(Optional.of(\"gcr\"));\n    Mockito.when(baseImageCredHelperConfig.getEnvironment())\n        .thenReturn(Collections.singletonMap(\"ENV_VARIABLE\", \"Value1\"));\n    Mockito.when(jibPluginConfiguration.getBaseImageCredHelperConfig())\n        .thenReturn(baseImageCredHelperConfig);\n\n    CredHelperConfiguration targetImageCredHelperConfig =\n        Mockito.mock(CredHelperConfiguration.class);\n    Mockito.when(targetImageCredHelperConfig.getHelperName()).thenReturn(Optional.of(\"gcr\"));\n    Mockito.when(targetImageCredHelperConfig.getEnvironment())\n        .thenReturn(Collections.singletonMap(\"ENV_VARIABLE\", \"Value2\"));\n    Mockito.when(jibPluginConfiguration.getTargetImageCredentialHelperConfig())\n        .thenReturn(targetImageCredHelperConfig);\n\n    Mockito.when(jibPluginConfiguration.getEntrypoint()).thenReturn(Arrays.asList(\"java\", \"Main\"));\n    Mockito.when(jibPluginConfiguration.getEnvironment())\n        .thenReturn(new HashMap<>(ImmutableMap.of(\"currency\", \"dollar\")));\n    Mockito.when(jibPluginConfiguration.getExposedPorts()).thenReturn(Arrays.asList(\"80/tcp\", \"0\"));\n    Mockito.when(jibPluginConfiguration.getJvmFlags()).thenReturn(Arrays.asList(\"-cp\", \".\"));\n    Mockito.when(jibPluginConfiguration.getLabels())\n        .thenReturn(new HashMap<>(ImmutableMap.of(\"unit\", \"cm\")));\n    Mockito.when(jibPluginConfiguration.getMainClass()).thenReturn(\"com.example.Main\");\n    Mockito.when(jibPluginConfiguration.getTargetImageAdditionalTags())\n        .thenReturn(new HashSet<>(Arrays.asList(\"additional\", \"tags\")));\n    Mockito.when(jibPluginConfiguration.getUser()).thenReturn(\"admin:wheel\");\n    Mockito.when(jibPluginConfiguration.getFilesModificationTime())\n        .thenReturn(\"2011-12-03T22:42:05Z\");\n    Mockito.when(jibPluginConfiguration.getDockerClientExecutable()).thenReturn(Paths.get(\"test\"));\n    Mockito.when(jibPluginConfiguration.getDockerClientEnvironment())\n        .thenReturn(new HashMap<>(ImmutableMap.of(\"docker\", \"client\")));\n    Mockito.when(jibPluginConfiguration.getDigestOutputPath()).thenReturn(Paths.get(\"digest/path\"));\n    Mockito.when(jibPluginConfiguration.getImageIdOutputPath()).thenReturn(Paths.get(\"id/path\"));\n    Mockito.when(jibPluginConfiguration.getImageJsonOutputPath())\n        .thenReturn(Paths.get(\"json/path\"));\n    Mockito.when(jibPluginConfiguration.getTarOutputPath()).thenReturn(Paths.get(\"tar/path\"));\n\n    MavenRawConfiguration rawConfiguration = new MavenRawConfiguration(jibPluginConfiguration);\n\n    AuthProperty fromAuth = rawConfiguration.getFromAuth();\n    Assert.assertEquals(\"user\", fromAuth.getUsername());\n    Assert.assertEquals(\"password\", fromAuth.getPassword());\n    Assert.assertEquals(\"<from><auth>\", fromAuth.getAuthDescriptor());\n    Assert.assertEquals(\"<from><auth><username>\", fromAuth.getUsernameDescriptor());\n    Assert.assertEquals(\"<from><auth><password>\", fromAuth.getPasswordDescriptor());\n\n    Assert.assertTrue(rawConfiguration.getAllowInsecureRegistries());\n    Assert.assertEquals(Arrays.asList(\"java\", \"Main\"), rawConfiguration.getEntrypoint().get());\n    Assert.assertEquals(\n        new HashMap<>(ImmutableMap.of(\"currency\", \"dollar\")), rawConfiguration.getEnvironment());\n    Assert.assertEquals(\"/app/root\", rawConfiguration.getAppRoot());\n    Assert.assertEquals(\"gcr\", rawConfiguration.getFromCredHelper().getHelperName().get());\n    Assert.assertEquals(\n        Collections.singletonMap(\"ENV_VARIABLE\", \"Value1\"),\n        rawConfiguration.getFromCredHelper().getEnvironment());\n    Assert.assertEquals(\"gcr\", rawConfiguration.getToCredHelper().getHelperName().get());\n    Assert.assertEquals(\n        Collections.singletonMap(\"ENV_VARIABLE\", \"Value2\"),\n        rawConfiguration.getToCredHelper().getEnvironment());\n    Assert.assertEquals(\"openjdk:15\", rawConfiguration.getFromImage().get());\n    Assert.assertEquals(Arrays.asList(\"-cp\", \".\"), rawConfiguration.getJvmFlags());\n    Assert.assertEquals(new HashMap<>(ImmutableMap.of(\"unit\", \"cm\")), rawConfiguration.getLabels());\n    Assert.assertEquals(\"com.example.Main\", rawConfiguration.getMainClass().get());\n    Assert.assertEquals(Arrays.asList(\"80/tcp\", \"0\"), rawConfiguration.getPorts());\n    Assert.assertEquals(\n        Arrays.asList(\"--log\", \"info\"), rawConfiguration.getProgramArguments().get());\n    Assert.assertEquals(\n        new HashSet<>(Arrays.asList(\"additional\", \"tags\")),\n        Sets.newHashSet(rawConfiguration.getToTags()));\n    Assert.assertEquals(\"admin:wheel\", rawConfiguration.getUser().get());\n    Assert.assertEquals(\"2011-12-03T22:42:05Z\", rawConfiguration.getFilesModificationTime());\n    Assert.assertEquals(Paths.get(\"test\"), rawConfiguration.getDockerExecutable().get());\n    Assert.assertEquals(\n        new HashMap<>(ImmutableMap.of(\"docker\", \"client\")),\n        rawConfiguration.getDockerEnvironment());\n    Assert.assertEquals(Paths.get(\"digest/path\"), jibPluginConfiguration.getDigestOutputPath());\n    Assert.assertEquals(Paths.get(\"id/path\"), jibPluginConfiguration.getImageIdOutputPath());\n    Assert.assertEquals(Paths.get(\"json/path\"), jibPluginConfiguration.getImageJsonOutputPath());\n    Assert.assertEquals(Paths.get(\"tar/path\"), jibPluginConfiguration.getTarOutputPath());\n\n    Mockito.verifyNoMoreInteractions(eventHandlers);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenSettingsProxyProviderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport org.apache.maven.plugin.MojoExecutionException;\nimport org.apache.maven.settings.Proxy;\nimport org.apache.maven.settings.Settings;\nimport org.apache.maven.settings.crypto.SettingsDecrypter;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.runner.RunWith;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Test for {@link MavenSettingsProxyProvider}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class MavenSettingsProxyProviderTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n\n  private static Settings noActiveProxiesSettings;\n  private static Settings httpOnlyProxySettings;\n  private static Settings httpsOnlyProxySettings;\n  private static Settings mixedProxyEncryptedSettings;\n  private static Settings badProxyEncryptedSettings;\n  private static SettingsDecrypter settingsDecrypter;\n  private static SettingsDecrypter emptySettingsDecrypter;\n\n  @BeforeClass\n  public static void setUpTestFixtures() {\n    noActiveProxiesSettings =\n        SettingsFixture.newSettings(\n            Paths.get(\"src/test/resources/maven/settings/no-active-proxy-settings.xml\"));\n    httpOnlyProxySettings =\n        SettingsFixture.newSettings(\n            Paths.get(\"src/test/resources/maven/settings/http-only-proxy-settings.xml\"));\n    httpsOnlyProxySettings =\n        SettingsFixture.newSettings(\n            Paths.get(\"src/test/resources/maven/settings/https-only-proxy-settings.xml\"));\n    mixedProxyEncryptedSettings =\n        SettingsFixture.newSettings(\n            Paths.get(\"src/test/resources/maven/settings/encrypted-proxy-settings.xml\"));\n    badProxyEncryptedSettings =\n        SettingsFixture.newSettings(\n            Paths.get(\"src/test/resources/maven/settings/bad-encrypted-proxy-settings.xml\"));\n    settingsDecrypter =\n        SettingsFixture.newSettingsDecrypter(\n            Paths.get(\"src/test/resources/maven/settings/settings-security.xml\"));\n    emptySettingsDecrypter =\n        SettingsFixture.newSettingsDecrypter(\n            Paths.get(\"src/test/resources/maven/settings/settings-security.empty.xml\"));\n  }\n\n  @Before\n  public void setUp() {\n    Arrays.asList(\n            \"http.proxyHost\",\n            \"http.proxyPort\",\n            \"http.proxyUser\",\n            \"http.proxyPassword\",\n            \"https.proxyHost\",\n            \"https.proxyPort\",\n            \"https.proxyUser\",\n            \"https.proxyPassword\",\n            \"http.nonProxyHosts\")\n        .forEach(System::clearProperty);\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet() {\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpHostSet() {\n    System.setProperty(\"http.proxyHost\", \"host\");\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpsHostSet() {\n    System.setProperty(\"https.proxyHost\", \"host\");\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpPortSet() {\n    System.setProperty(\"http.proxyPort\", \"port\");\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpsPortSet() {\n    System.setProperty(\"https.proxyPort\", \"port\");\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpUserSet() {\n    System.setProperty(\"http.proxyUser\", \"user\");\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpsUserSet() {\n    System.setProperty(\"https.proxyUser\", \"user\");\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpPasswordSet() {\n    System.setProperty(\"http.proxyPassword\", \"password\");\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_httpsPasswordSet() {\n    System.setProperty(\"https.proxyPassword\", \"password\");\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertTrue(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testAreProxyPropertiesSet_ignoresHttpNonProxyHosts() {\n    System.setProperty(\"http.nonProxyHosts\", \"non proxy hosts\");\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"http\"));\n    Assert.assertFalse(MavenSettingsProxyProvider.areProxyPropertiesSet(\"https\"));\n  }\n\n  @Test\n  public void testSetProxyProperties() {\n    Proxy httpProxy = new Proxy();\n    httpProxy.setProtocol(\"http\");\n    httpProxy.setHost(\"host\");\n    httpProxy.setPort(1080);\n    httpProxy.setUsername(\"user\");\n    httpProxy.setPassword(\"pass\");\n    httpProxy.setNonProxyHosts(\"non proxy hosts\");\n\n    MavenSettingsProxyProvider.setProxyProperties(httpProxy);\n    Assert.assertEquals(\"host\", System.getProperty(\"http.proxyHost\"));\n    Assert.assertEquals(\"1080\", System.getProperty(\"http.proxyPort\"));\n    Assert.assertEquals(\"user\", System.getProperty(\"http.proxyUser\"));\n    Assert.assertEquals(\"pass\", System.getProperty(\"http.proxyPassword\"));\n    Assert.assertEquals(\"non proxy hosts\", System.getProperty(\"http.nonProxyHosts\"));\n\n    Proxy httpsProxy = new Proxy();\n    httpsProxy.setProtocol(\"https\");\n    httpsProxy.setHost(\"https host\");\n    httpsProxy.setPort(1443);\n    httpsProxy.setUsername(\"https user\");\n    httpsProxy.setPassword(\"https pass\");\n    MavenSettingsProxyProvider.setProxyProperties(httpsProxy);\n    Assert.assertEquals(\"https host\", System.getProperty(\"https.proxyHost\"));\n    Assert.assertEquals(\"1443\", System.getProperty(\"https.proxyPort\"));\n    Assert.assertEquals(\"https user\", System.getProperty(\"https.proxyUser\"));\n    Assert.assertEquals(\"https pass\", System.getProperty(\"https.proxyPassword\"));\n  }\n\n  @Test\n  public void testSetProxyProperties_someValuesUndefined() {\n    Proxy httpProxy = new Proxy();\n    httpProxy.setProtocol(\"http\");\n    httpProxy.setHost(\"http://host\");\n\n    MavenSettingsProxyProvider.setProxyProperties(httpProxy);\n    Assert.assertEquals(\"http://host\", System.getProperty(\"http.proxyHost\"));\n    Assert.assertNull(System.getProperty(\"http.proxyUser\"));\n    Assert.assertNull(System.getProperty(\"http.proxyPassword\"));\n    Assert.assertNull(System.getProperty(\"http.nonProxyHosts\"));\n\n    Proxy httpsProxy = new Proxy();\n    httpsProxy.setProtocol(\"https\");\n    httpsProxy.setUsername(\"https user\");\n    httpsProxy.setPassword(\"https pass\");\n    MavenSettingsProxyProvider.setProxyProperties(httpsProxy);\n    Assert.assertNull(System.getProperty(\"https.proxyHost\"));\n    Assert.assertEquals(\"https user\", System.getProperty(\"https.proxyUser\"));\n    Assert.assertEquals(\"https pass\", System.getProperty(\"https.proxyPassword\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_noActiveProxy() throws MojoExecutionException {\n\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        noActiveProxiesSettings, settingsDecrypter);\n\n    Assert.assertNull(System.getProperty(\"http.proxyHost\"));\n    Assert.assertNull(System.getProperty(\"https.proxyHost\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_firstActiveHttpProxy() throws MojoExecutionException {\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        httpOnlyProxySettings, settingsDecrypter);\n\n    Assert.assertEquals(\"proxy2.example.com\", System.getProperty(\"http.proxyHost\"));\n    Assert.assertNull(System.getProperty(\"https.proxyHost\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_firstActiveHttpsProxy()\n      throws MojoExecutionException {\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        httpsOnlyProxySettings, settingsDecrypter);\n\n    Assert.assertEquals(\"proxy2.example.com\", System.getProperty(\"https.proxyHost\"));\n    Assert.assertNull(System.getProperty(\"http.proxyHost\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_encryptedProxy() throws MojoExecutionException {\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        mixedProxyEncryptedSettings, settingsDecrypter);\n\n    Assert.assertEquals(\"password1\", System.getProperty(\"http.proxyPassword\"));\n    Assert.assertEquals(\"password2\", System.getProperty(\"https.proxyPassword\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_dontOverwriteUserHttp()\n      throws MojoExecutionException {\n    System.setProperty(\"http.proxyHost\", \"host\");\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        mixedProxyEncryptedSettings, settingsDecrypter);\n\n    Assert.assertNull(System.getProperty(\"http.proxyPassword\"));\n    Assert.assertEquals(\"password2\", System.getProperty(\"https.proxyPassword\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_dontOverwriteUserHttps()\n      throws MojoExecutionException {\n    System.setProperty(\"https.proxyHost\", \"host\");\n    MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n        mixedProxyEncryptedSettings, settingsDecrypter);\n\n    Assert.assertEquals(\"password1\", System.getProperty(\"http.proxyPassword\"));\n    Assert.assertNull(System.getProperty(\"https.proxyPassword\"));\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_decryptionFailure() {\n    try {\n      MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n          badProxyEncryptedSettings, settingsDecrypter);\n      Assert.fail();\n    } catch (MojoExecutionException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\"Unable to decrypt proxy info from settings.xml:\"));\n    }\n  }\n\n  @Test\n  public void testActivateHttpAndHttpsProxies_emptySettingsDecrypter() {\n    try {\n      MavenSettingsProxyProvider.activateHttpAndHttpsProxies(\n          mixedProxyEncryptedSettings, emptySettingsDecrypter);\n      Assert.fail();\n    } catch (MojoExecutionException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\"Unable to decrypt proxy info from settings.xml:\"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenSettingsServerCredentialsTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.plugins.common.AuthProperty;\nimport com.google.cloud.tools.jib.plugins.common.InferredAuthException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/** Tests for {@link MavenSettingsServerCredentials}. */\npublic class MavenSettingsServerCredentialsTest {\n\n  private MavenSettingsServerCredentials mavenSettingsServerCredentialsNoMasterPassword;\n  private MavenSettingsServerCredentials mavenSettingsServerCredentials;\n  private Path testSettings = Paths.get(\"src/test/resources/maven/settings/settings.xml\");\n  private Path testSettingsSecurity =\n      Paths.get(\"src/test/resources/maven/settings/settings-security.xml\");\n  private Path testSettingsSecurityEmpty =\n      Paths.get(\"src/test/resources/maven/settings/settings-security.empty.xml\");\n\n  @Before\n  public void setUp() {\n    mavenSettingsServerCredentials =\n        new MavenSettingsServerCredentials(\n            SettingsFixture.newSettings(testSettings),\n            SettingsFixture.newSettingsDecrypter(testSettingsSecurity));\n    mavenSettingsServerCredentialsNoMasterPassword =\n        new MavenSettingsServerCredentials(\n            SettingsFixture.newSettings(testSettings),\n            SettingsFixture.newSettingsDecrypter(testSettingsSecurityEmpty));\n  }\n\n  @Test\n  public void testInferredAuth_decrypterFailure() {\n    try {\n      mavenSettingsServerCredentials.inferAuth(\"badServer\");\n      Assert.fail();\n    } catch (InferredAuthException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\"Unable to decrypt server(badServer) info from settings.xml:\"));\n    }\n  }\n\n  @Test\n  public void testInferredAuth_successEncrypted() throws InferredAuthException {\n    Optional<AuthProperty> auth = mavenSettingsServerCredentials.inferAuth(\"encryptedServer\");\n    Assert.assertTrue(auth.isPresent());\n    Assert.assertEquals(\"encryptedUser\", auth.get().getUsername());\n    Assert.assertEquals(\"password1\", auth.get().getPassword());\n  }\n\n  @Test\n  public void testInferredAuth_successUnencrypted() throws InferredAuthException {\n    Optional<AuthProperty> auth = mavenSettingsServerCredentials.inferAuth(\"simpleServer\");\n    Assert.assertTrue(auth.isPresent());\n    Assert.assertEquals(\"simpleUser\", auth.get().getUsername());\n    Assert.assertEquals(\"password2\", auth.get().getPassword());\n  }\n\n  @Test\n  public void testInferredAuth_successNoPasswordDoesNotBlowUp() throws InferredAuthException {\n    Optional<AuthProperty> auth =\n        mavenSettingsServerCredentialsNoMasterPassword.inferAuth(\"simpleServer\");\n    Assert.assertTrue(auth.isPresent());\n    Assert.assertEquals(\"simpleUser\", auth.get().getUsername());\n    Assert.assertEquals(\"password2\", auth.get().getPassword());\n  }\n\n  @Test\n  public void testInferredAuth_registryWithHostAndPort() throws InferredAuthException {\n    Optional<AuthProperty> auth =\n        mavenSettingsServerCredentialsNoMasterPassword.inferAuth(\"docker.example.com:8080\");\n    Assert.assertTrue(auth.isPresent());\n    Assert.assertEquals(\"registryUser\", auth.get().getUsername());\n    Assert.assertEquals(\"registryPassword\", auth.get().getPassword());\n  }\n\n  @Test\n  public void testInferredAuth_registryWithHostWithoutPort() throws InferredAuthException {\n    Optional<AuthProperty> auth =\n        mavenSettingsServerCredentialsNoMasterPassword.inferAuth(\"docker.example.com\");\n    Assert.assertTrue(auth.isPresent());\n    Assert.assertEquals(\"registryUser\", auth.get().getUsername());\n    Assert.assertEquals(\"registryPassword\", auth.get().getPassword());\n  }\n\n  @Test\n  public void testInferredAuth_registrySettingsWithPort() throws InferredAuthException {\n    // Attempt to resolve WITHOUT the port. Should work as well.\n    Optional<AuthProperty> auth =\n        mavenSettingsServerCredentialsNoMasterPassword.inferAuth(\"docker.example.com:5432\");\n    Assert.assertTrue(auth.isPresent());\n    Assert.assertEquals(\"registryUser\", auth.get().getUsername());\n    Assert.assertEquals(\"registryPassword\", auth.get().getPassword());\n  }\n\n  @Test\n  public void testInferredAuth_notFound() throws InferredAuthException {\n    Assert.assertFalse(mavenSettingsServerCredentials.inferAuth(\"serverUnknown\").isPresent());\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MojoCommonTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.ProjectInfo;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.plugins.common.ProjectProperties;\nimport com.google.common.util.concurrent.Futures;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n@RunWith(MockitoJUnitRunner.class)\npublic class MojoCommonTest {\n\n  @Mock private ProjectProperties mockProjectProperties;\n\n  @Test\n  public void testFinishUpdateChecker_correctMessageLogged() {\n    when(mockProjectProperties.getToolName()).thenReturn(\"tool-name\");\n    when(mockProjectProperties.getToolVersion()).thenReturn(\"2.0.0\");\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.of(\"2.1.0\"));\n    MojoCommon.finishUpdateChecker(mockProjectProperties, updateCheckFuture);\n\n    verify(mockProjectProperties)\n        .log(\n            LogEvent.lifecycle(\n                \"\\u001B[33mA new version of tool-name (2.1.0) is available (currently using 2.0.0). \"\n                    + \"Update your build configuration to use the latest features and fixes!\\u001B[0m\"));\n    verify(mockProjectProperties)\n        .log(\n            LogEvent.lifecycle(\n                \"\\u001B[33m\"\n                    + ProjectInfo.GITHUB_URL\n                    + \"/blob/master/jib-maven-plugin/CHANGELOG.md\\u001B[0m\"));\n    verify(mockProjectProperties)\n        .log(\n            LogEvent.lifecycle(\n                \"Please see \"\n                    + ProjectInfo.GITHUB_URL\n                    + \"/blob/master/docs/privacy.md for info on disabling this update check.\"));\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/SettingsFixture.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.common.base.Preconditions;\nimport java.lang.reflect.Field;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport org.apache.maven.settings.Settings;\nimport org.apache.maven.settings.building.DefaultSettingsBuilderFactory;\nimport org.apache.maven.settings.building.DefaultSettingsBuildingRequest;\nimport org.apache.maven.settings.building.SettingsBuilder;\nimport org.apache.maven.settings.building.SettingsBuildingException;\nimport org.apache.maven.settings.building.SettingsBuildingRequest;\nimport org.apache.maven.settings.crypto.DefaultSettingsDecrypter;\nimport org.apache.maven.settings.crypto.SettingsDecrypter;\nimport org.sonatype.plexus.components.cipher.DefaultPlexusCipher;\nimport org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;\nimport org.sonatype.plexus.components.sec.dispatcher.PasswordDecryptor;\n\nclass SettingsFixture {\n\n  /**\n   * Create a new {@link Settings} for testing purposes.\n   *\n   * @param settingsFile absolute path to settings.xml\n   * @return {@link Settings} built from settingsFile\n   */\n  static Settings newSettings(Path settingsFile) {\n    Preconditions.checkArgument(Files.isRegularFile(settingsFile));\n    try {\n      SettingsBuilder settingsBuilder = new DefaultSettingsBuilderFactory().newInstance();\n      SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest();\n      settingsRequest.setUserSettingsFile(settingsFile.toFile());\n      return settingsBuilder.build(settingsRequest).getEffectiveSettings();\n    } catch (SettingsBuildingException ex) {\n      throw new IllegalStateException(\"Tests need to be rewritten: \" + ex.getMessage(), ex);\n    }\n  }\n\n  /**\n   * Create a new {@link SettingsDecrypter} for testing purposes.\n   *\n   * @param settingsSecurityFile absolute path to security-settings.xml\n   * @return {@link SettingsDecrypter} built from settingsSecurityFile\n   */\n  static SettingsDecrypter newSettingsDecrypter(Path settingsSecurityFile) {\n    Preconditions.checkArgument(Files.isRegularFile(settingsSecurityFile));\n    try {\n\n      DefaultPlexusCipher injectCypher = new DefaultPlexusCipher();\n      DefaultSecDispatcher injectedDispatcher =\n          new DefaultSecDispatcher(\n              injectCypher,\n              new HashMap<String, PasswordDecryptor>(),\n              settingsSecurityFile.toAbsolutePath().toString());\n      setField(DefaultSecDispatcher.class, injectedDispatcher, \"_cipher\", injectCypher);\n\n      return new DefaultSettingsDecrypter(injectedDispatcher);\n    } catch (Exception ex) {\n      throw new IllegalStateException(\"Tests need to be rewritten: \" + ex.getMessage(), ex);\n    }\n  }\n\n  /** Inject fields into object that would have otherwise been injected by the build system. */\n  private static <T> void setField(\n      Class<T> clazz, T instance, String fieldName, Object injectedField)\n      throws NoSuchFieldException, IllegalAccessException {\n    Field field = clazz.getDeclaredField(fieldName);\n    field.setAccessible(true);\n    field.set(instance, injectedField);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/SkippedGoalVerifier.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\n\n/** A simple verifier utility to test goal skipping across all our jib goals. */\nclass SkippedGoalVerifier {\n\n  /** Verifies that a Jib goal is skipped with {@code jib.skip=true}. */\n  static void verifyJibSkip(TestProject testProject, String goal)\n      throws VerificationException, IOException {\n    Verifier verifier = new Verifier(testProject.getProjectRoot().toString());\n    verifier.setAutoclean(false);\n    verifier.setSystemProperty(\"jib.skip\", \"true\");\n\n    verifier.executeGoal(\"jib:\" + goal);\n\n    Path logFile = Paths.get(verifier.getBasedir(), verifier.getLogFileName());\n    MatcherAssert.assertThat(\n        new String(Files.readAllBytes(logFile), StandardCharsets.UTF_8),\n        CoreMatchers.containsString(\n            \"[INFO] Skipping containerization because jib-maven-plugin: skip = true\\n\"\n                + \"[INFO] ------------------------------------------------------------------------\\n\"\n                + \"[INFO] BUILD SUCCESS\"));\n  }\n\n  /** Verifies that a Jib goal is skipped with {@code jib.containerize=noGroup:noArtifact}. */\n  static void verifyJibContainerizeSkips(TestProject testProject, String goal)\n      throws VerificationException, IOException {\n    Verifier verifier = new Verifier(testProject.getProjectRoot().toString());\n    verifier.setAutoclean(false);\n    // noGroup:noArtifact should never match\n    verifier.setSystemProperty(\"jib.containerize\", \"noGroup:noArtifact\");\n\n    verifier.executeGoal(\"jib:\" + goal);\n\n    Path logFile = Paths.get(verifier.getBasedir(), verifier.getLogFileName());\n    MatcherAssert.assertThat(\n        new String(Files.readAllBytes(logFile), StandardCharsets.UTF_8),\n        CoreMatchers.containsString(\n            \"[INFO] Skipping containerization of this module (not specified in jib.containerize)\\n\"\n                + \"[INFO] ------------------------------------------------------------------------\\n\"\n                + \"[INFO] BUILD SUCCESS\"));\n  }\n\n  private SkippedGoalVerifier() {}\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/TestProject.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Properties;\nimport org.apache.maven.it.util.ResourceExtractor;\nimport org.junit.rules.TemporaryFolder;\n\n/** Works with the test Maven projects in the {@code resources/projects} directory. */\npublic class TestProject extends TemporaryFolder implements Closeable {\n\n  private static final String PROJECTS_PATH_IN_RESOURCES = \"/maven/projects/\";\n\n  private static boolean isPomXml(Path path) {\n    String filename = path.getFileName().toString();\n    return filename.startsWith(\"pom\") && filename.endsWith(\".xml\");\n  }\n\n  private final String projectDir;\n\n  private Path projectRoot;\n\n  /** Initialize to a specific project directory. */\n  public TestProject(String projectDir) {\n    this.projectDir = projectDir;\n  }\n\n  /** Gets the project root resolved as a real path. */\n  public Path getProjectRoot() throws IOException {\n    return projectRoot.toRealPath();\n  }\n\n  @Override\n  public void close() {\n    after();\n  }\n\n  @Override\n  protected void before() throws Throwable {\n    super.before();\n\n    copyProject();\n  }\n\n  private void copyProject() throws IOException {\n    projectRoot =\n        ResourceExtractor.extractResourcePath(\n                TestProject.class, PROJECTS_PATH_IN_RESOURCES + projectDir, newFolder(), true)\n            .toPath();\n\n    // Puts the correct plugin version into the test project pom.xml.\n    Path gradleProperties = Paths.get(\"gradle.properties\");\n    Properties properties = new Properties();\n    properties.load(Files.newInputStream(gradleProperties));\n    String pluginVersion = properties.getProperty(\"version\");\n\n    new DirectoryWalker(projectRoot)\n        .filter(TestProject::isPomXml)\n        .walk(\n            pomXml ->\n                Files.write(\n                    pomXml,\n                    new String(Files.readAllBytes(pomXml), StandardCharsets.UTF_8)\n                        .replace(\"@@PluginVersion@@\", pluginVersion)\n                        .getBytes(StandardCharsets.UTF_8)));\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/TestRepository.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven;\n\nimport com.google.common.io.Resources;\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Path;\nimport org.apache.maven.artifact.Artifact;\nimport org.apache.maven.artifact.DefaultArtifact;\nimport org.apache.maven.artifact.handler.ArtifactHandler;\nimport org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;\nimport org.apache.maven.artifact.repository.ArtifactRepository;\nimport org.apache.maven.artifact.repository.ArtifactRepositoryFactory;\nimport org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;\nimport org.apache.maven.artifact.resolver.ArtifactResolutionRequest;\nimport org.apache.maven.artifact.resolver.ArtifactResolutionResult;\nimport org.apache.maven.artifact.resolver.ArtifactResolver;\nimport org.apache.maven.plugin.testing.MojoRule;\nimport org.codehaus.plexus.component.repository.exception.ComponentLookupException;\nimport org.junit.Assert;\nimport org.junit.rules.ExternalResource;\n\n/** A test helper to resolve artifacts from a local repository in test/resources. */\npublic class TestRepository extends ExternalResource {\n\n  private static final String TEST_M2 = \"maven/testM2\";\n\n  private ArtifactRepository testLocalRepo;\n  private ArtifactResolver artifactResolver;\n  private ArtifactHandler jarHandler;\n\n  @Override\n  protected void before()\n      throws ComponentLookupException, URISyntaxException, MalformedURLException {\n    MojoRule testHarness = new MojoRule();\n    ArtifactRepositoryFactory artifactRepositoryFactory =\n        testHarness.lookup(ArtifactRepositoryFactory.class);\n    artifactResolver = testHarness.lookup(ArtifactResolver.class);\n    jarHandler = testHarness.lookup(ArtifactHandlerManager.class).getArtifactHandler(\"jar\");\n    testLocalRepo =\n        artifactRepositoryFactory.createArtifactRepository(\n            \"test\",\n            Resources.getResource(TEST_M2).toURI().toURL().toString(),\n            new DefaultRepositoryLayout(),\n            null,\n            null);\n  }\n\n  Artifact findArtifact(String group, String artifact, String version) {\n    ArtifactResolutionRequest artifactResolutionRequest = new ArtifactResolutionRequest();\n    artifactResolutionRequest.setLocalRepository(testLocalRepo);\n    Artifact artifactToFind =\n        new DefaultArtifact(group, artifact, version, null, \"jar\", null, jarHandler);\n\n    artifactResolutionRequest.setArtifact(artifactToFind);\n\n    ArtifactResolutionResult ars = artifactResolver.resolve(artifactResolutionRequest);\n\n    Assert.assertEquals(1, ars.getArtifacts().size());\n    return ars.getArtifacts().iterator().next();\n  }\n\n  Path artifactPathOnDisk(String group, String artifact, String version) {\n    return findArtifact(group, artifact, version).getFile().toPath();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/CheckJibVersionMojoTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.MojoCommon;\nimport com.google.cloud.tools.jib.maven.TestProject;\nimport java.io.IOException;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link CheckJibVersionMojo}. */\npublic class CheckJibVersionMojoTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @Test\n  public void testIdentifiers() {\n    // These identifiers will be baked into Skaffold and should not be changed\n    Assert.assertEquals(\"_skaffold-fail-if-jib-out-of-date\", CheckJibVersionMojo.GOAL_NAME);\n    Assert.assertEquals(\"jib.requiredVersion\", MojoCommon.REQUIRED_VERSION_PROPERTY_NAME);\n  }\n\n  @Test\n  public void testFailOnMissingProperty() throws VerificationException, IOException {\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    try {\n      verifier.executeGoal(\"jib:\" + CheckJibVersionMojo.GOAL_NAME);\n      Assert.fail(\"build should have failed\");\n    } catch (VerificationException ex) {\n      verifier.verifyTextInLog(\"requires jib.requiredVersion to be set\");\n    }\n  }\n\n  @Test\n  public void testFailOnOutOfDate() throws VerificationException, IOException {\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(MojoCommon.REQUIRED_VERSION_PROPERTY_NAME, \"[,1.0)\");\n    try {\n      verifier.executeGoal(\"jib:\" + CheckJibVersionMojo.GOAL_NAME);\n      Assert.fail(\"build should have failed\");\n    } catch (VerificationException ex) {\n      verifier.verifyTextInLog(\"but is required to be [,1.0)\");\n    }\n  }\n\n  @Test\n  public void testSuccess() throws VerificationException, IOException {\n    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());\n    verifier.setSystemProperty(MojoCommon.REQUIRED_VERSION_PROPERTY_NAME, \"[1.0,)\");\n    verifier.executeGoal(\"jib:\" + CheckJibVersionMojo.GOAL_NAME);\n    verifier.verifyErrorFreeLog();\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2KotlinTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.common.collect.ImmutableSet;\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.apache.maven.model.Plugin;\nimport org.apache.maven.model.PluginExecution;\nimport org.apache.maven.project.MavenProject;\nimport org.codehaus.plexus.util.xml.Xpp3Dom;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Kotlin-related tests for {@link FilesMojoV2}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class FilesMojoV2KotlinTest {\n\n  private final PluginExecution pluginExecution1 = new PluginExecution();\n  private final PluginExecution pluginExecution2 = new PluginExecution();\n  private final Xpp3Dom configuration1 = new Xpp3Dom(\"configuration\");\n  private final Xpp3Dom configuration2 = new Xpp3Dom(\"configuration\");\n  private final Xpp3Dom sourceDirs1 = new Xpp3Dom(\"sourceDirs\");\n  private final Xpp3Dom sourceDirs2 = new Xpp3Dom(\"sourceDirs\");\n  private final Xpp3Dom sourceDir1 = new Xpp3Dom(\"sourceDir\");\n  private final Xpp3Dom sourceDir2 = new Xpp3Dom(\"sourceDir\");\n  private final Xpp3Dom sourceDir3 = new Xpp3Dom(\"sourceDir\");\n  private final Xpp3Dom sourceDir4 = new Xpp3Dom(\"sourceDir\");\n\n  @Mock private MavenProject mavenProject;\n  @Mock private Plugin kotlinPlugin;\n\n  @Before\n  public void setUp() {\n    Mockito.when(mavenProject.getPlugin(\"org.jetbrains.kotlin:kotlin-maven-plugin\"))\n        .thenReturn(kotlinPlugin);\n    Mockito.when(mavenProject.getBasedir()).thenReturn(new File(\"/base\"));\n\n    pluginExecution1.setConfiguration(configuration1);\n    pluginExecution2.setConfiguration(configuration2);\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_noKotlinPlugin() {\n    Mockito.when(mavenProject.getPlugin(Mockito.anyString())).thenReturn(null);\n    Assert.assertEquals(ImmutableSet.of(), FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_noExecutions() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Collections.emptyList());\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_noConfiguration() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n    pluginExecution1.setConfiguration(null);\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_noSourceDirs() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_noSourceDirsChildren() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n    configuration1.addChild(sourceDirs1);\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_nullSourceDir() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n    configuration1.addChild(sourceDirs1);\n    sourceDirs1.addChild(sourceDir1);\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_emptySourceDir() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n    configuration1.addChild(sourceDirs1);\n    sourceDirs1.addChild(sourceDir1);\n    sourceDir1.setValue(\"\");\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_relativePath() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n    configuration1.addChild(sourceDirs1);\n    sourceDirs1.addChild(sourceDir1);\n    sourceDir1.setValue(\"kotlin/src\");\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\"), Paths.get(\"/base/kotlin/src\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_absolutePath() {\n    Mockito.when(kotlinPlugin.getExecutions()).thenReturn(Arrays.asList(pluginExecution1));\n    configuration1.addChild(sourceDirs1);\n    sourceDirs1.addChild(sourceDir1);\n    sourceDir1.setValue(\"/absolute/src\");\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\"), Paths.get(\"/absolute/src\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_complex() {\n    Mockito.when(kotlinPlugin.getExecutions())\n        .thenReturn(Arrays.asList(pluginExecution1, pluginExecution2));\n    configuration1.addChild(sourceDirs1);\n    configuration2.addChild(sourceDirs2);\n    sourceDirs1.addChild(sourceDir1);\n    sourceDirs1.addChild(sourceDir2);\n    sourceDirs2.addChild(sourceDir3);\n    sourceDirs2.addChild(sourceDir4);\n    sourceDir1.setValue(\"/absolute/src1\");\n    sourceDir2.setValue(\"relative/src2\");\n    sourceDir3.setValue(\"/absolute/src3\");\n    sourceDir4.setValue(\"relative/src4\");\n\n    Assert.assertEquals(\n        ImmutableSet.of(\n            Paths.get(\"/base/src/main/kotlin\"),\n            Paths.get(\"/absolute/src1\"),\n            Paths.get(\"/absolute/src3\"),\n            Paths.get(\"/base/relative/src2\"),\n            Paths.get(\"/base/relative/src4\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_noDuplicates() {\n    Mockito.when(kotlinPlugin.getExecutions())\n        .thenReturn(Arrays.asList(pluginExecution1, pluginExecution2));\n    configuration1.addChild(sourceDirs1);\n    configuration2.addChild(sourceDirs2);\n    sourceDirs1.addChild(sourceDir1);\n    sourceDirs1.addChild(sourceDir2);\n    sourceDirs2.addChild(sourceDir3);\n    sourceDirs2.addChild(sourceDir4);\n    sourceDir1.setValue(\"src/main/kotlin\");\n    sourceDir2.setValue(\"/base/another/src\");\n    sourceDir3.setValue(\"another/src\");\n    sourceDir4.setValue(\"/base/src/main/kotlin\");\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\"), Paths.get(\"/base/another/src\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n\n  @Test\n  public void getKotlinSourceDirectories_excludeTestCompileGoal() {\n    Mockito.when(kotlinPlugin.getExecutions())\n        .thenReturn(Arrays.asList(pluginExecution1, pluginExecution2));\n    pluginExecution1.setGoals(Arrays.asList(\"compile\"));\n    pluginExecution2.setGoals(Arrays.asList(\"tomato\", \"test-compile\"));\n    configuration1.addChild(sourceDirs1);\n    configuration2.addChild(sourceDirs2);\n    sourceDirs1.addChild(sourceDir1);\n    sourceDirs2.addChild(sourceDir2);\n    sourceDir1.setValue(\"/included\");\n    sourceDir2.setValue(\"/should/not/be/included\");\n\n    Assert.assertEquals(\n        ImmutableSet.of(Paths.get(\"/base/src/main/kotlin\"), Paths.get(\"/included\")),\n        FilesMojoV2.getKotlinSourceDirectories(mavenProject));\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2Test.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.TestProject;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldFilesOutput;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link FilesMojoV2}. */\npublic class FilesMojoV2Test {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi\");\n\n  private static void verifyFiles(\n      Path projectRoot,\n      String pomXml,\n      String module,\n      List<String> extraCliOptions,\n      List<String> buildFiles,\n      List<String> inputFiles,\n      List<String> ignoreFiles)\n      throws VerificationException, IOException {\n\n    Verifier verifier = new Verifier(projectRoot.toString());\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"--file=\" + pomXml);\n    verifier.addCliOption(\"-q\");\n    if (!Strings.isNullOrEmpty(module)) {\n      verifier.addCliOption(\"-pl\");\n      verifier.addCliOption(module);\n      verifier.addCliOption(\"-am\");\n    }\n    extraCliOptions.forEach(verifier::addCliOption);\n    verifier.executeGoal(\"jib:\" + FilesMojoV2.GOAL_NAME);\n\n    verifier.verifyErrorFreeLog();\n    Path logFile = Paths.get(verifier.getBasedir()).resolve(verifier.getLogFileName());\n    List<String> log = Files.readAllLines(logFile, StandardCharsets.UTF_8);\n\n    int begin = log.indexOf(\"BEGIN JIB JSON\");\n    Assert.assertTrue(begin > -1);\n    SkaffoldFilesOutput output = new SkaffoldFilesOutput(log.get(begin + 1));\n    Assert.assertEquals(buildFiles, output.getBuild());\n    Assert.assertEquals(inputFiles, output.getInputs());\n    Assert.assertEquals(ignoreFiles, output.getIgnore());\n  }\n\n  @Test\n  public void testFilesMojo_singleModule() throws VerificationException, IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n\n    verifyFiles(\n        projectRoot,\n        \"pom.xml\",\n        null,\n        Collections.emptyList(),\n        Collections.singletonList(projectRoot.resolve(\"pom.xml\").toString()),\n        Arrays.asList(\n            projectRoot.resolve(\"src/main/java\").toString(),\n            projectRoot.resolve(\"src/main/resources\").toString(),\n            projectRoot.resolve(\"src/main/jib-custom\").toString()),\n        Collections.emptyList());\n  }\n\n  @Test\n  public void testFilesMojo_singleModuleWithMultipleExtraDirectories()\n      throws VerificationException, IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n\n    verifyFiles(\n        projectRoot,\n        \"pom-extra-dirs.xml\",\n        null,\n        Collections.emptyList(),\n        Collections.singletonList(projectRoot.resolve(\"pom-extra-dirs.xml\").toString()),\n        Arrays.asList(\n            projectRoot.resolve(\"src/main/java\").toString(),\n            projectRoot.resolve(\"src/main/resources\").toString(),\n            projectRoot.resolve(\"src/main/jib-custom\").toString(),\n            projectRoot.resolve(\"src/main/jib-custom-2\").toString()),\n        Collections.emptyList());\n  }\n\n  @Test\n  public void testFilesMojo_multiModuleSimpleService() throws VerificationException, IOException {\n    Path projectRoot = multiTestProject.getProjectRoot();\n    Path simpleServiceRoot = projectRoot.resolve(\"simple-service\");\n\n    verifyFiles(\n        projectRoot,\n        \"pom.xml\",\n        \"simple-service\",\n        Collections.emptyList(),\n        Arrays.asList(\n            projectRoot.resolve(\"pom.xml\").toString(),\n            simpleServiceRoot.resolve(\"pom.xml\").toString()),\n        Arrays.asList(\n            simpleServiceRoot.resolve(\"src/main/java\").toString(),\n            simpleServiceRoot.resolve(\"src/main/resources\").toString(),\n            simpleServiceRoot.resolve(\"src/main/jib\").toString()),\n        Collections.emptyList());\n  }\n\n  @Test\n  public void testFilesMojo_multiModuleComplexService() throws VerificationException, IOException {\n    Path projectRoot = multiTestProject.getProjectRoot();\n    Path complexServiceRoot = projectRoot.resolve(\"complex-service\");\n    Path libRoot = projectRoot.resolve(\"lib\");\n\n    verifyFiles(\n        projectRoot,\n        \"pom.xml\",\n        \"complex-service\",\n        Collections.emptyList(),\n        Arrays.asList(\n            projectRoot.resolve(\"pom.xml\").toString(),\n            libRoot.resolve(\"pom.xml\").toString(),\n            complexServiceRoot.resolve(\"pom.xml\").toString()),\n        Arrays.asList(\n            libRoot.resolve(\"src/main/java\").toString(),\n            libRoot.resolve(\"src/main/resources\").toString(),\n            complexServiceRoot.resolve(\"src/main/java\").toString(),\n            complexServiceRoot.resolve(\"src/main/resources1\").toString(),\n            complexServiceRoot.resolve(\"src/main/resources2\").toString(),\n            complexServiceRoot.resolve(\"src/main/jib1\").toString(),\n            complexServiceRoot.resolve(\"src/main/jib2\").toString(),\n            // this test expects standard .m2 locations\n            Paths.get(\n                    System.getProperty(\"user.home\"),\n                    \".m2/repository/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.jar\")\n                .toString()),\n        Collections.emptyList());\n  }\n\n  @Test\n  public void testFilesMojo_extraDirectoriesProperty() throws VerificationException, IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n\n    verifyFiles(\n        projectRoot,\n        \"pom.xml\",\n        null,\n        Collections.singletonList(\n            \"-Djib.extraDirectories.paths=/some/extra/dir,/another/extra/dir\"),\n        Collections.singletonList(projectRoot.resolve(\"pom.xml\").toString()),\n        Arrays.asList(\n            projectRoot.resolve(\"src/main/java\").toString(),\n            projectRoot.resolve(\"src/main/resources\").toString(),\n            Paths.get(\"/\").toAbsolutePath().resolve(\"some/extra/dir\").toString(),\n            Paths.get(\"/\").toAbsolutePath().resolve(\"another/extra/dir\").toString()),\n        Collections.emptyList());\n  }\n\n  @Test\n  public void testFilesMojo_skaffoldConfigProperties() throws VerificationException, IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n\n    verifyFiles(\n        projectRoot,\n        \"pom-skaffold-config.xml\",\n        null,\n        Collections.emptyList(),\n        Arrays.asList(\n            projectRoot.resolve(\"pom-skaffold-config.xml\").toString(),\n            Paths.get(\"/abs/path/some.xml\").toAbsolutePath().toString()),\n        Arrays.asList(\n            projectRoot.resolve(\"src/main/java\").toString(),\n            projectRoot.resolve(\"src/main/resources\").toString(),\n            projectRoot.resolve(\"src/main/jib-custom\").toString(),\n            projectRoot.resolve(\"file/in/project\").toString()),\n        Arrays.asList(\n            projectRoot.resolve(\"file/to/exclude\").toString(),\n            projectRoot.resolve(\"file/to/also/exclude\").toString()));\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/InitMojoTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.TestProject;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldInitOutput;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link InitMojo}. */\npublic class InitMojoTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi\");\n\n  @ClassRule\n  public static final TestProject springTestProject = new TestProject(\"spring-boot-multi\");\n\n  /**\n   * Verifies that the files task succeeded and returns the list of JSON strings printed by the\n   * task.\n   *\n   * @param project the project to run the task on\n   * @return the JSON strings printed by the task\n   */\n  private static List<String> getJsons(TestProject project)\n      throws VerificationException, IOException {\n    Verifier verifier = new Verifier(project.getProjectRoot().toString());\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-q\");\n    verifier.addCliOption(\"-Dimage=testimage\");\n    verifier.executeGoal(\"jib:\" + InitMojo.GOAL_NAME);\n\n    verifier.verifyErrorFreeLog();\n    Path logFile = Paths.get(verifier.getBasedir()).resolve(verifier.getLogFileName());\n    String output = String.join(\"\\n\", Files.readAllLines(logFile, StandardCharsets.UTF_8)).trim();\n    MatcherAssert.assertThat(output, CoreMatchers.containsString(\"BEGIN JIB JSON\"));\n\n    Pattern pattern = Pattern.compile(\".*BEGIN JIB JSON\\r?\\n(\\\\{.*})\");\n    Matcher matcher = pattern.matcher(output);\n    List<String> jsons = new ArrayList<>();\n    while (matcher.find()) {\n      jsons.add(matcher.group(1));\n    }\n\n    return jsons;\n  }\n\n  @Test\n  public void testFilesMojo_singleModule() throws IOException, VerificationException {\n    List<String> outputs = getJsons(simpleTestProject);\n    Assert.assertEquals(1, outputs.size());\n\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(0));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"com.test:hello-world\", skaffoldInitOutput.getProject());\n  }\n\n  @Test\n  public void testFilesMojo_multiModule() throws IOException, VerificationException {\n    List<String> outputs = getJsons(multiTestProject);\n    Assert.assertEquals(3, outputs.size());\n\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(0));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"com.jib.test:simple-service\", skaffoldInitOutput.getProject());\n\n    skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(1));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"com.jib.test:lib\", skaffoldInitOutput.getProject());\n\n    skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(2));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"com.jib.test:complex-service\", skaffoldInitOutput.getProject());\n  }\n\n  @Test\n  public void testFilesMojo_multiModule_differentParent()\n      throws IOException, VerificationException {\n    List<String> outputs = getJsons(springTestProject);\n    Assert.assertEquals(2, outputs.size());\n\n    SkaffoldInitOutput skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(0));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"org.springframework.boot:service-1\", skaffoldInitOutput.getProject());\n\n    skaffoldInitOutput = new SkaffoldInitOutput(outputs.get(1));\n    Assert.assertEquals(\"testimage\", skaffoldInitOutput.getImage());\n    Assert.assertEquals(\"org.springframework.boot:service-2\", skaffoldInitOutput.getProject());\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/PackageGoalsMojoTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport com.google.cloud.tools.jib.maven.TestProject;\nimport com.google.common.base.Strings;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link PackageGoalsMojo}. */\npublic class PackageGoalsMojoTest {\n\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi\");\n\n  private void verifyGoals(Path projectRoot, String profilesString, String... expectedGoals)\n      throws VerificationException, IOException {\n    Verifier verifier = new Verifier(projectRoot.toString());\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-q\");\n    verifier.addCliOption(\"-pl\");\n    verifier.addCliOption(\"complex-service\");\n    verifier.addCliOption(\"-am\");\n    if (!Strings.isNullOrEmpty(profilesString)) {\n      verifier.addCliOption(\"-P\" + profilesString);\n    }\n    verifier.executeGoal(\"jib:\" + PackageGoalsMojo.GOAL_NAME);\n\n    verifier.verifyErrorFreeLog();\n    Path logFile = Paths.get(verifier.getBasedir()).resolve(verifier.getLogFileName());\n    List<String> log = Files.readAllLines(logFile, StandardCharsets.UTF_8);\n    if (log.size() != 0 && log.get(0).startsWith(\"Picked up JAVA_TOOL_OPTIONS:\")) {\n      log.remove(0);\n    }\n\n    Assert.assertEquals(Arrays.asList(expectedGoals), log);\n  }\n\n  @Test\n  public void testPackageGoalsMojo_complexServiceDefault()\n      throws VerificationException, IOException {\n    verifyGoals(multiTestProject.getProjectRoot(), null);\n  }\n\n  @Test\n  public void testPackageGoalsMojo_complexServiceLocalProfile()\n      throws VerificationException, IOException {\n    verifyGoals(multiTestProject.getProjectRoot(), \"localJib\", \"dockerBuild\");\n  }\n\n  @Test\n  public void testPackageGoalsMojo_complexServiceRemoteProfile()\n      throws VerificationException, IOException {\n    verifyGoals(multiTestProject.getProjectRoot(), \"remoteJib\", \"build\");\n  }\n\n  @Test\n  public void testPackageGoalsMojo_complexServiceMultipleProfiles()\n      throws VerificationException, IOException {\n    verifyGoals(multiTestProject.getProjectRoot(), \"localJib,remoteJib\", \"dockerBuild\", \"build\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/SyncMapMojoTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.skaffold;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.maven.TestProject;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldSyncMapTemplate;\nimport com.google.cloud.tools.jib.plugins.common.SkaffoldSyncMapTemplate.FileTemplate;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.List;\nimport org.apache.maven.it.VerificationException;\nimport org.apache.maven.it.Verifier;\nimport org.junit.Assert;\nimport org.junit.ClassRule;\nimport org.junit.Test;\n\n/** Tests for {@link SyncMapMojo}. */\npublic class SyncMapMojoTest {\n\n  @ClassRule public static final TestProject simpleTestProject = new TestProject(\"simple\");\n  @ClassRule public static final TestProject multiTestProject = new TestProject(\"multi\");\n  @ClassRule public static final TestProject warProject = new TestProject(\"war_servlet25\");\n\n  private static Path runBuild(Path projectRoot, String module, String pomXml)\n      throws VerificationException {\n    Verifier verifier = new Verifier(projectRoot.toString());\n    verifier.setAutoclean(false);\n    verifier.addCliOption(\"-q\");\n    if (pomXml != null) {\n      verifier.addCliOption(\"--file=\" + pomXml);\n    }\n    if (!Strings.isNullOrEmpty(module)) {\n      verifier.addCliOption(\"-pl\");\n      verifier.addCliOption(module);\n      verifier.addCliOption(\"-am\");\n      verifier.addCliOption(\"-Djib.containerize=com.jib.test:\" + module);\n    }\n    verifier.addCliOption(\"-DskipTests\");\n    verifier.executeGoals(ImmutableList.of(\"package\", \"jib:\" + SyncMapMojo.GOAL_NAME));\n\n    return Paths.get(verifier.getBasedir()).resolve(verifier.getLogFileName());\n  }\n\n  private static String getSyncMapJson(Path projectRoot, String module, String pomXml)\n      throws VerificationException, IOException {\n    Path logFile = runBuild(projectRoot, module, pomXml);\n    List<String> outputLines = Files.readAllLines(logFile, StandardCharsets.UTF_8);\n    if (outputLines.size() != 0 && outputLines.get(0).startsWith(\"Picked up JAVA_TOOL_OPTIONS:\")) {\n      outputLines.remove(0);\n    }\n    Assert.assertEquals(3, outputLines.size()); // we expect [\"\\n\", \"<marker>\", \"<sync-json>\"]\n    Assert.assertEquals(\"BEGIN JIB JSON: SYNCMAP/1\", outputLines.get(1));\n    return outputLines.get(2); // this is the JSON output\n  }\n\n  private static void assertFilePaths(Path src, AbsoluteUnixPath dest, FileTemplate template) {\n    Assert.assertEquals(src.toString(), template.getSrc());\n    Assert.assertEquals(dest.toString(), template.getDest());\n  }\n\n  @Test\n  public void testSyncMapMojo_simpleTestProjectOutput() throws IOException, VerificationException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n    String json = getSyncMapJson(projectRoot, null, null);\n    SkaffoldSyncMapTemplate parsed = SkaffoldSyncMapTemplate.from(json);\n\n    List<FileTemplate> generated = parsed.getGenerated();\n    Assert.assertEquals(2, generated.size());\n    assertFilePaths(\n        projectRoot.resolve(\"target/classes/world\"),\n        AbsoluteUnixPath.get(\"/app/resources/world\"),\n        generated.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"target/classes/com/test/HelloWorld.class\"),\n        AbsoluteUnixPath.get(\"/app/classes/com/test/HelloWorld.class\"),\n        generated.get(1));\n\n    List<FileTemplate> direct = parsed.getDirect();\n    Assert.assertEquals(2, direct.size());\n    assertFilePaths(\n        projectRoot.resolve(\"src/main/jib-custom/bar/cat\"),\n        AbsoluteUnixPath.get(\"/bar/cat\"),\n        direct.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"src/main/jib-custom/foo\"),\n        AbsoluteUnixPath.get(\"/foo\"),\n        direct.get(1));\n  }\n\n  @Test\n  public void testSyncMapMojo_multiProjectOutput() throws IOException, VerificationException {\n    Path projectRoot = multiTestProject.getProjectRoot();\n    Path m2 = Paths.get(System.getProperty(\"user.home\")).resolve(\".m2\").resolve(\"repository\");\n    String json = getSyncMapJson(projectRoot, \"complex-service\", null);\n    SkaffoldSyncMapTemplate parsed = SkaffoldSyncMapTemplate.from(json);\n\n    List<FileTemplate> generated = parsed.getGenerated();\n    Assert.assertEquals(2, generated.size());\n    assertFilePaths(\n        projectRoot.resolve(\"lib/target/lib-1.0.0.TEST-SNAPSHOT.jar\"),\n        AbsoluteUnixPath.get(\"/app/libs/lib-1.0.0.TEST-SNAPSHOT.jar\"),\n        generated.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"complex-service/target/classes/com/test/HelloWorld.class\"),\n        AbsoluteUnixPath.get(\"/app/classes/com/test/HelloWorld.class\"),\n        generated.get(1));\n\n    List<FileTemplate> direct = parsed.getDirect();\n    Assert.assertEquals(3, direct.size());\n    assertFilePaths(\n        m2.resolve(\n            \"com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.jar\"),\n        AbsoluteUnixPath.get(\"/app/libs/tiny-test-lib-0.0.1-SNAPSHOT.jar\"),\n        direct.get(0));\n    assertFilePaths(\n        projectRoot.resolve(\"complex-service/src/main/jib1/foo\"),\n        AbsoluteUnixPath.get(\"/foo\"),\n        direct.get(1));\n    assertFilePaths(\n        projectRoot.resolve(\"complex-service/src/main/jib2/bar\"),\n        AbsoluteUnixPath.get(\"/bar\"),\n        direct.get(2));\n  }\n\n  @Test\n  public void testSyncMapMojo_skaffoldConfig() throws IOException, VerificationException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n    String json = getSyncMapJson(projectRoot, null, \"pom-skaffold-config.xml\");\n    SkaffoldSyncMapTemplate parsed = SkaffoldSyncMapTemplate.from(json);\n\n    List<FileTemplate> generated = parsed.getGenerated();\n    Assert.assertEquals(1, generated.size());\n    assertFilePaths(\n        projectRoot.resolve(\"target/classes/world\"),\n        AbsoluteUnixPath.get(\"/app/resources/world\"),\n        generated.get(0));\n    // target/classes/com/test ignored\n\n    List<FileTemplate> direct = parsed.getDirect();\n    Assert.assertEquals(1, direct.size());\n    assertFilePaths(\n        projectRoot.resolve(\"src/main/jib-custom/bar/cat\"),\n        AbsoluteUnixPath.get(\"/bar/cat\"),\n        direct.get(0));\n    // src/main/jib-custom/foo is ignored\n  }\n\n  @Test\n  public void testSyncMapMojo_failIfPackagingNotJar() throws IOException {\n    Path projectRoot = warProject.getProjectRoot();\n    VerificationException ve =\n        assertThrows(VerificationException.class, () -> runBuild(projectRoot, null, null));\n    assertThat(ve)\n        .hasMessageThat()\n        .contains(\n            \"MojoExecutionException: Skaffold sync is currently only available for 'jar' style Jib \"\n                + \"projects, but the packaging of servlet25 is 'war'\");\n  }\n\n  @Test\n  public void testSyncMapMojo_failIfJarContainerizationMode() throws IOException {\n    Path projectRoot = simpleTestProject.getProjectRoot();\n    VerificationException ve =\n        assertThrows(\n            VerificationException.class,\n            () -> runBuild(projectRoot, null, \"pom-jar-containerization.xml\"));\n    assertThat(ve)\n        .hasMessageThat()\n        .contains(\n            \"MojoExecutionException: Skaffold sync is currently only available for Jib projects in \"\n                + \"'exploded' containerizing mode, but the containerizing mode of hello-world is \"\n                + \"'packaged'\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/dependencies/library.jarC.jar",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/dependencies/libraryA.jar",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/dependencies/libraryB.jar",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/dependencies/more/dependency-1.0.0.jar",
    "content": "]e$\u000fỀx\u0017,\u001b\n.3I݅3\u001c8V\u0014KA\u0003M\u0007)=5~'qю$[-\t:&% \u001cEo\u00067Ns`iZ\u00040\u0019MT.9J[}?\u000f\\E\f\u0019}UvJd\u000eo(i\u0019\"Mԛ_+/\u001dcI<Zje44%\u001fd2\u000e?l>.-\bO=\u0013Hi"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/output/directory/somefile",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/output/resourceA",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/output/resourceB",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/output/world",
    "content": "world"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/source/HelloWorld.java",
    "content": "\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/source/package/some.java",
    "content": "\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/application/source/some.java",
    "content": "\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/default-target/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>default-target-name</artifactId>\n  <version>default-target-version</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:8-jdk-focal</image>\n          </from>\n          <container>\n            <args>An argument.</args>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n            <volumes>\n              <volume>/var/log</volume>\n              <volume>/var/log2</volume>\n            </volumes>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/default-target/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\nimport dependency.Greeting;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/** Example class that uses a dependency and a resource file. */\npublic class HelloWorld {\n\n  public static void main(String[] args) throws IOException, URISyntaxException {\n    // 'Greeting' comes from the dependency artfiact.\n    String greeting = Greeting.getGreeting();\n\n    // Gets the contents of the resource file 'world'.\n    ClassLoader classLoader = HelloWorld.class.getClassLoader();\n    Path worldFile = Paths.get(classLoader.getResource(\"world\").toURI());\n    String world = new String(Files.readAllBytes(worldFile), StandardCharsets.UTF_8);\n\n    System.out.println(greeting + \", \" + world + \". \" + (args.length > 0 ? args[0] : \"\"));\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/default-target/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/empty/pom-broken-user.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>empty</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <user>myuser:mygroup</user>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/empty/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>empty</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:11-jdk-focal</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n            <tags>${_ADDITIONAL_TAG}</tags>\n          </to>\n          <container>\n            <user>12345:54321</user>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n            <volumes>\n              <volume>/var/log</volume>\n              <volume>/var/log2</volume>\n            </volumes>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/empty/src/main/java/com/test/Empty.java",
    "content": "package com.test;\n\npublic class Empty {\n\n  public static void main(String[] args) {}\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/fake-remote-repo/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata modelVersion=\"1.1.0\">\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>tiny-test-lib</artifactId>\n  <version>0.0.1-SNAPSHOT</version>\n  <versioning>\n    <snapshot>\n      <localCopy>true</localCopy>\n    </snapshot>\n    <lastUpdated>20190911205316</lastUpdated>\n    <snapshotVersions>\n      <snapshotVersion>\n        <extension>jar</extension>\n        <value>0.0.1-SNAPSHOT</value>\n        <updated>20190911205316</updated>\n      </snapshotVersion>\n      <snapshotVersion>\n        <extension>pom</extension>\n        <value>0.0.1-SNAPSHOT</value>\n        <updated>20190911205316</updated>\n      </snapshotVersion>\n    </snapshotVersions>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/fake-remote-repo/com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.pom",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>tiny-test-lib</artifactId>\n  <version>0.0.1-SNAPSHOT</version>\n  <description>POM was created from install:install-file</description>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/fake-remote-repo/com/google/cloud/tools/tiny-test-lib/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n  <groupId>com.google.cloud.tools</groupId>\n  <artifactId>tiny-test-lib</artifactId>\n  <versioning>\n    <versions>\n      <version>0.0.1-SNAPSHOT</version>\n    </versions>\n    <lastUpdated>20190911205316</lastUpdated>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <artifactId>complex-service</artifactId>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n\n  <parent>\n    <groupId>com.jib.test</groupId>\n    <artifactId>multimodule</artifactId>\n    <version>1.0.0.TEST-SNAPSHOT</version>\n  </parent>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <configuration>\n          <extraDirectories>\n            <paths>\n              <path>src/main/jib1</path> <!-- should resolve to this projectRoot -->\n              <path>${project.basedir}/src/main/jib2</path> <!-- provided as absolute within project -->\n            </paths>\n          </extraDirectories>\n        </configuration>\n      </plugin>\n    </plugins>\n    <resources>\n      <resource>\n        <directory>src/main/resources1</directory>\n      </resource>\n      <resource>\n        <directory>src/main/resources2</directory>\n      </resource>\n    </resources>\n  </build>\n\n  <profiles>\n    <profile>\n      <id>localJib</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.google.cloud.tools</groupId>\n            <artifactId>jib-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <phase>package</phase>\n                <goals><goal>dockerBuild</goal></goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>remoteJib</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.google.cloud.tools</groupId>\n            <artifactId>jib-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <phase>package</phase>\n                <goals><goal>build</goal></goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n  <!-- The fake remote repo contains one tiny test library installed with\n    mvn org.apache.maven.plugins:maven-install-plugin:2.3.1:install-file \\\n        -Dfile=tiny.jar -DlocalRepositoryPath=fake-remote-repo/ \\\n        -DgroupId=com.google.cloud.tools -DartifactId=tiny-test-lib \\\n        -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar\n\n    it will be treated as a remote dependency and copied into the user's local\n    maven repository.\n   -->\n  <repositories>\n    <repository>\n      <id>fake-remote-repo</id>\n      <url>file:${project.basedir}/fake-remote-repo</url>\n    </repository>\n  </repositories>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.jib.test</groupId>\n      <artifactId>lib</artifactId>\n      <version>1.0.0.TEST-SNAPSHOT</version>\n      <scope>compile</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-io</artifactId>\n      <version>1.3.2</version>\n      <scope>compile</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud.tools</groupId>\n      <artifactId>tiny-test-lib</artifactId>\n      <version>0.0.1-SNAPSHOT</version>\n      <scope>compile</scope>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/extra-resources-1/resource1.txt",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/extra-resources-2/resource2.txt",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib1/foo",
    "content": "foo"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib2/bar",
    "content": "bar\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/lib/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <artifactId>lib</artifactId>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n  <name>lib</name>\n\n  <parent>\n    <groupId>com.jib.test</groupId>\n    <artifactId>multimodule</artifactId>\n    <version>1.0.0.TEST-SNAPSHOT</version>\n  </parent>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.1</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/lib/src/main/java/com/lib/Lib.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.lib;\n\n/** Shared Code! */\npublic class Lib {\n\n  public String getThing() {\n    return \"thing\";\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/lib/src/test/java/com/lib/LibTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.lib;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Unit test for simple App. */\npublic class LibTest {\n  /** Rigorous Test :-) */\n  @Test\n  public void testGetThing() {\n    Assert.assertEquals(\"thing\", new Lib().getThing());\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n                             http://maven.apache.org/maven-v4_0_0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this parent POM. -->\n  <groupId>com.jib.test</groupId>\n  <artifactId>multimodule</artifactId>\n  <packaging>pom</packaging>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n  <name>multimodule</name>\n\n  <properties>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n  </properties>\n\n  <!-- The modules that are part of this project. -->\n  <modules>\n    <module>simple-service</module>\n    <module>complex-service</module>\n    <module>lib</module>\n  </modules>\n\n  <build>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.google.cloud.tools</groupId>\n          <artifactId>jib-maven-plugin</artifactId>\n          <version>@@PluginVersion@@</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n\n  <repositories>\n     <repository>\n       <id>snapshots-repo</id>\n       <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n       <releases>\n         <enabled>false</enabled>\n       </releases>\n       <snapshots>\n         <enabled>true</enabled>\n       </snapshots>\n     </repository>\n   </repositories>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/simple-service/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this module. -->\n  <artifactId>simple-service</artifactId>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n\n  <!-- Inherits from the Jib Multimodule parent POM. -->\n  <parent>\n    <groupId>com.jib.test</groupId>\n    <artifactId>multimodule</artifactId>\n    <version>1.0.0.TEST-SNAPSHOT</version>\n  </parent>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/multi/simple-service/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/mock-docker.sh",
    "content": "#!/bin/bash\n\nif [[ \"$1\" == \"info\" ]]; then\n  # Output the JSON string\n  echo \"{\\\"OSType\\\":\\\"linux\\\",\\\"Architecture\\\":\\\"x86_64\\\"}\"\n  exit 0\nfi\n\n# Read stdin to avoid broken pipe\ncat > /dev/null\n\necho \"Docker load called. $envvar1 $envvar2\""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-properties.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n    <jib.from.image>${_DOCKER_HOST}:5000/distroless/java</jib.from.image>\n    <jib.from.auth.username>testuser</jib.from.auth.username>\n    <jib.from.auth.password>testpassword</jib.from.auth.password>\n    <jib.to.image>${_TARGET_IMAGE}</jib.to.image>\n    <jib.to.auth.username>${_TARGET_USERNAME}</jib.to.auth.username>\n    <jib.to.auth.password>${_TARGET_PASSWORD}</jib.to.auth.password>\n    <jib.container.args>An argument.</jib.container.args>\n    <jib.container.mainClass>com.test.HelloWorld</jib.container.mainClass>\n    <jib.container.jvmFlags>-Xms512m,-Xdebug</jib.container.jvmFlags>\n    <jib.container.environment>env1=envvalue1,env2=envvalue2</jib.container.environment>\n    <jib.container.ports>1000/tcp,2000-2003/udp</jib.container.ports>\n    <jib.container.labels>key1=value1,key2=value2</jib.container.labels>\n    <jib.container.volumes>/var/log,/var/log2</jib.container.volumes>\n    <jib.container.format>Docker</jib.container.format>\n    <jib.extraDirectories.paths>${project.basedir}/src/main/jib-custom</jib.extraDirectories.paths>\n    <jib.extraDirectories.permissions>/foo=755,/bar/cat=777</jib.extraDirectories.permissions>\n    <jib.allowInsecureRegistries>true</jib.allowInsecureRegistries>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>${_DOCKER_HOST}:5000/distroless/java</image>\n            <auth>\n              <username>testuser</username>\n              <password>testpassword</password>\n            </auth>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n            <auth>\n              <username>${_TARGET_USERNAME}</username>\n              <password>${_TARGET_PASSWORD}</password>\n            </auth>\n          </to>\n          <container>\n            <creationTime>USE_CURRENT_TIMESTAMP</creationTime>\n            <args>\n              <arg>An argument.</arg>\n            </args>\n            <mainClass>com.test.HelloWorld</mainClass>\n            <extraClasspath><path>/other</path></extraClasspath>\n            <jvmFlags>\n              <jvmFlag>-Xms512m</jvmFlag>\n              <jvmFlag>-Xdebug</jvmFlag>\n            </jvmFlags>\n            <environment>\n              <env1>envvalue1</env1>\n              <env2>envvalue2</env2>\n            </environment>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n            <volumes>\n              <volume>/var/log</volume>\n              <volume>/var/log2</volume>\n            </volumes>\n            <format>Docker</format>\n          </container>\n          <extraDirectories>\n            <paths><path>${project.basedir}/src/main/jib-custom</path></paths>\n            <permissions>\n              <permission>\n                <file>/foo</file>\n                <mode>755</mode>\n              </permission>\n              <permission>\n                <file>/bar/cat</file>\n                <mode>777</mode>\n              </permission>\n            </permissions>\n          </extraDirectories>\n          <outputPaths>\n            <digest>${project.build.directory}/different-jib-image.digest</digest>\n            <imageId>different-jib-image.id</imageId>\n          </outputPaths>\n          <allowInsecureRegistries>true</allowInsecureRegistries>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.test</groupId>\n    <artifactId>my-artifact-id</artifactId>\n    <version>1</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.test</groupId>\n            <artifactId>dependency</artifactId>\n            <version>1.0.0</version>\n            <scope>system</scope>\n            <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.8</source>\n                    <target>1.8</target>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>com.google.cloud.tools</groupId>\n                <artifactId>jib-maven-plugin</artifactId>\n                <version>${jib-maven-plugin.version}</version>\n                <configuration>\n                    <from>\n                        <image>eclipse-temurin:8-jdk-focal</image>\n                    </from>\n                    <to>\n                        <image>${_TARGET_IMAGE}</image>\n                        <credHelper>gcr</credHelper>\n                    </to>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.test</groupId>\n    <artifactId>my-artifact-id</artifactId>\n    <version>1</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.test</groupId>\n            <artifactId>dependency</artifactId>\n            <version>1.0.0</version>\n            <scope>system</scope>\n            <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.8</source>\n                    <target>1.8</target>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>com.google.cloud.tools</groupId>\n                <artifactId>jib-maven-plugin</artifactId>\n                <version>${jib-maven-plugin.version}</version>\n                <configuration>\n                    <from>\n                        <image>eclipse-temurin:8-jdk-focal</image>\n                    </from>\n                    <to>\n                        <image>${_TARGET_IMAGE}</image>\n                        <credHelper>\n                            <helper>gcr</helper>\n                            <environment>\n                                <ENV_VAR>A VAR</ENV_VAR>\n                            </environment>\n                        </credHelper>\n                    </to>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-dockerclient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\t\n          <source>1.8</source>\t\n          <target>1.8</target>\t\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <dockerClient>\n            <executable>${project.basedir}/mock-docker.sh</executable>\n            <environment>\n              <envvar1>value1</envvar1>\n              <envvar2>value2</envvar2>\n            </environment>\n          </dockerClient>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-extra-dirs-filtering.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>my-artifact-id</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n    <jib.to.image>${_TARGET_IMAGE}</jib.to.image>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>busybox</image>\n          </from>\n          <extraDirectories>\n            <paths>\n              <path>\n                <from>${project.basedir}/src/main/jib-custom-3</from>\n                <into>/extras</into>\n                <includes>**/*a*,*.txt</includes>\n                <excludes>**/*.txt</excludes>\n              </path>\n              <path>\n                <from>src/main/jib-custom-4</from>\n                <into>/extras</into>\n                <includes>\n                  <include>foo</include>\n                </includes>\n              </path>\n            </paths>\n          </extraDirectories>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-extra-dirs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>my-artifact-id</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n    <jib.to.image>${_TARGET_IMAGE}</jib.to.image>\n    <jib.container.args>An argument.</jib.container.args>\n    <jib.container.ports>1000/tcp,2000-2003/udp</jib.container.ports>\n    <jib.container.labels>key1=value1,key2=value2</jib.container.labels>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96</image>\n          </from>\n          <extraDirectories>\n            <paths>\n              <path>${project.basedir}/src/main/jib-custom</path>\n              <path>\n                <from>${project.basedir}/src/main/jib-custom-2</from>\n                <into>/custom/target</into>\n              </path>\n            </paths>\n          </extraDirectories>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-jar-containerization.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <version>3.2.0</version>\n        <configuration>\n          <archive>\n            <manifest>\n              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n            </manifest>\n          </archive>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:11-jdk-focal</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <containerizingMode>packaged</containerizingMode>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11-incompatible.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>my-artifact-id</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.8.0</version>\n        <configuration>\n          <release>11</release>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:8-jdk-focal</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <args>An argument.</args>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>my-artifact-id</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.8.0</version>\n        <configuration>\n          <release>11</release>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:11-jdk-focal</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <args>An argument.</args>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-localbase.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>${_BASE_IMAGE}</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <creationTime>EPOCH</creationTime>\n            <args>An argument.</args>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n            <volumes>\n              <volume>/var/log</volume>\n              <volume>/var/log2</volume>\n            </volumes>\n            <workingDirectory>/home</workingDirectory>\n          </container>\n          <extraDirectories>\n            <paths><path>${project.basedir}/src/main/jib-custom</path></paths>\n          </extraDirectories>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>my-artifact-id</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.8.0</version>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:11</image>\n            <platforms>\n              <platform>\n                <architecture>arm64</architecture>\n                <os>linux</os>\n              </platform>\n              <platform>\n                <architecture>amd64</architecture>\n                <os>linux</os>\n              </platform>\n            </platforms>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n             <tags>\n              <tag>latest</tag>\n              <tag>another</tag>\n            </tags>\n          </to>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-no-to-image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>my-artifact-id</artifactId>\n  <version>1</version>\n\n  <name>Name not Usable as Image Reference</name>\n  \n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>eclipse-temurin:11-jdk-focal</image>\n          </from>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-skaffold-config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.test</groupId>\n    <artifactId>hello-world</artifactId>\n    <version>1</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.test</groupId>\n            <artifactId>dependency</artifactId>\n            <version>1.0.0</version>\n            <scope>system</scope>\n            <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.8</source>\n                    <target>1.8</target>\n                </configuration>\n            </plugin>\n\n            <plugin>\n                <groupId>com.google.cloud.tools</groupId>\n                <artifactId>jib-maven-plugin</artifactId>\n                <version>${jib-maven-plugin.version}</version>\n                <configuration>\n                    <extraDirectories>\n                        <paths>\n                            <path>src/main/jib-custom</path>\n                        </paths>\n                    </extraDirectories>\n                    <skaffold>\n                        <watch>\n                            <buildIncludes>\n                                <buildInclude>/abs/path/some.xml</buildInclude>\n                            </buildIncludes>\n                            <includes>\n                                <includes>file/in/project</includes>\n                            </includes>\n                            <excludes>\n                                <excludes>file/to/exclude</excludes>\n                                <excludes>file/to/also/exclude</excludes>\n                            </excludes>\n                        </watch>\n                        <sync>\n                            <excludes>\n                                <exclude>target/classes/com/test</exclude>\n                                <exclude>src/main/jib-custom/foo</exclude>\n                            </excludes>\n                        </sync>\n                    </skaffold>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom-timestamps-custom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>${_DOCKER_HOST}:5000/distroless/java</image>\n            <auth>\n              <username>testuser</username>\n              <password>testpassword</password>\n            </auth>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n            <auth>\n              <username>${_TARGET_USERNAME}</username>\n              <password>${_TARGET_PASSWORD}</password>\n            </auth>\n          </to>\n          <container>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n            <filesModificationTime>2019-06-17T16:30:00Z</filesModificationTime>\n            <creationTime>2013-11-05T06:29:30Z</creationTime>\n          </container>\n          <extraDirectories>\n            <paths><path>src/main/jib-custom</path></paths>\n          </extraDirectories>\n          <allowInsecureRegistries>true</allowInsecureRegistries>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>hello-world</artifactId>\n  <version>1</version>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.test</groupId>\n      <artifactId>dependency</artifactId>\n      <version>1.0.0</version>\n      <scope>system</scope>\n      <systemPath>${project.basedir}/libs/dependency-1.0.0.jar</systemPath>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <creationTime>EPOCH</creationTime>\n            <args>An argument.</args>\n            <ports>\n              <port>1000/tcp</port>\n              <port>2000-2003/udp</port>\n            </ports>\n            <labels>\n              <key1>value1</key1>\n              <key2>value2</key2>\n            </labels>\n            <volumes>\n              <volume>/var/log</volume>\n              <volume>/var/log2</volume>\n            </volumes>\n            <workingDirectory>/home</workingDirectory>\n          </container>\n          <extraDirectories>\n            <paths><path>${project.basedir}/src/main/jib-custom</path></paths>\n          </extraDirectories>\n          <outputPaths>\n            <tar>${project.build.directory}/different-jib-image.tar</tar>\n          </outputPaths>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\nimport dependency.Greeting;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.management.ManagementFactory;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermissions;\n\n/** Example class that uses a dependency and a resource file. */\npublic class HelloWorld {\n\n  public static void main(String[] args) throws IOException, URISyntaxException {\n    // 'Greeting' comes from the dependency artfiact.\n    String greeting = Greeting.getGreeting();\n\n    // Gets the contents of the resource file 'world'.\n    try (BufferedReader reader =\n        new BufferedReader(\n            new InputStreamReader(\n                HelloWorld.class.getResourceAsStream(\"/world\"), StandardCharsets.UTF_8))) {\n      String world = reader.readLine();\n      System.out.println(greeting + \", \" + world + \". \" + (args.length > 0 ? args[0] : \"\"));\n      Path worldFilePath = Paths.get(\"/app/resources/world\");\n      if (worldFilePath.toFile().exists()) {\n        System.out.println(Files.getLastModifiedTime(worldFilePath).toString());\n      }\n\n      // Prints the contents of the extra files.\n      if (Files.exists(Paths.get(\"/foo\"))) {\n        System.out.println(\n            PosixFilePermissions.toString(Files.getPosixFilePermissions(Paths.get(\"/foo\"))));\n        System.out.println(\n            PosixFilePermissions.toString(Files.getPosixFilePermissions(Paths.get(\"/bar/cat\"))));\n        System.out.println(\n            new String(Files.readAllBytes(Paths.get(\"/foo\")), StandardCharsets.UTF_8));\n        System.out.println(\n            new String(Files.readAllBytes(Paths.get(\"/bar/cat\")), StandardCharsets.UTF_8));\n        System.out.println(Files.getLastModifiedTime(Paths.get(\"/foo\")).toString());\n        System.out.println(Files.getLastModifiedTime(Paths.get(\"/bar/cat\")).toString());\n      }\n      // Prints the contents of the files in the second extra directory.\n      if (Files.exists(Paths.get(\"/custom/target/baz\"))) {\n        System.out.println(\n            new String(\n                Files.readAllBytes(Paths.get(\"/custom/target/baz\")), StandardCharsets.UTF_8));\n        System.out.println(Files.getLastModifiedTime(Paths.get(\"/custom/target/baz\")).toString());\n      }\n\n      // Prints jvm flags\n      for (String jvmFlag : ManagementFactory.getRuntimeMXBean().getInputArguments()) {\n        System.out.println(jvmFlag);\n      }\n\n      if (System.getenv(\"env1\") != null) {\n        System.out.println(System.getenv(\"env1\"));\n      }\n      if (System.getenv(\"env2\") != null) {\n        System.out.println(System.getenv(\"env2\"));\n      }\n\n      Package pack = HelloWorld.class.getPackage();\n      if (pack.getImplementationTitle() != null) {\n        System.out.println(\"Implementation-Title: \" + pack.getImplementationTitle());\n        System.out.println(\"Implementation-Version: \" + pack.getImplementationVersion());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom/bar/cat",
    "content": "cat"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom/foo",
    "content": "foo"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-2/baz",
    "content": "baz"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-3/cat.json",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-3/cat.txt",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-3/sub/a.json",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-3/sub/a.txt",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-4/bar",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/jib-custom-4/foo",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/simple/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n        xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>example</groupId>\n    <artifactId>spring-boot</artifactId>\n    <version>0.1.0</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.1.6.RELEASE</version>\n    </parent>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n    </dependencies>\n\n    <properties>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <version>2.1.6.RELEASE</version>\n            </plugin>\n            <plugin>\n                <groupId>com.google.cloud.tools</groupId>\n                <artifactId>jib-maven-plugin</artifactId>\n                <version>@@PluginVersion@@</version>\n                <configuration>\n                  <from><image>gcr.io/distroless/java:debug</image></from>\n                  <to><image>${_TARGET_IMAGE}</image></to>\n                  <containerizingMode>packaged</containerizingMode>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot/src/main/java/hello/Application.java",
    "content": "package hello;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n  public static void main(String[] args) {\n    SpringApplication.run(Application.class, args);\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot/src/main/java/hello/HelloController.java",
    "content": "package hello;\n\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class HelloController {\n\n  @RequestMapping(\"/\")\n  public String index() {\n    return \"Hello world\";\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot-multi/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0\n                             http://maven.apache.org/maven-v4_0_0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <!-- Defines artifact information for this parent POM. -->\n  <groupId>com.jib.test</groupId>\n  <artifactId>spring-boot-multi</artifactId>\n  <packaging>pom</packaging>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n  <name>multimodule</name>\n\n  <properties>\n    <maven.compiler.source>1.8</maven.compiler.source>\n    <maven.compiler.target>1.8</maven.compiler.target>\n  </properties>\n\n  <!-- The modules that are part of this project. -->\n  <modules>\n    <module>service-1</module>\n    <module>service-2</module>\n  </modules>\n\n  <build>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.google.cloud.tools</groupId>\n          <artifactId>jib-maven-plugin</artifactId>\n          <version>@@PluginVersion@@</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot-multi/service-1/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <artifactId>service-1</artifactId>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n\n  <parent>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-parent</artifactId>\n    <version>2.1.6.RELEASE</version>\n  </parent>\n\n  <build>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.google.cloud.tools</groupId>\n          <artifactId>jib-maven-plugin</artifactId>\n          <version>@@PluginVersion@@</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot-multi/service-1/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot-multi/service-2/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <artifactId>service-2</artifactId>\n  <version>1.0.0.TEST-SNAPSHOT</version>\n\n  <parent>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-parent</artifactId>\n    <version>2.1.6.RELEASE</version>\n  </parent>\n\n  <build>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.google.cloud.tools</groupId>\n          <artifactId>jib-maven-plugin</artifactId>\n          <version>@@PluginVersion@@</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/spring-boot-multi/service-2/src/main/java/com/test/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.test;\n\npublic class HelloWorld {\n  public static void main(String[] args) {\n    System.out.println(\"Hello world\");\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom-tomcat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>servlet25</artifactId>\n  <version>1</version>\n  <packaging>war</packaging>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>5.0.0</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>  <!-- random dependency -->\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n      <version>2.1.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <from>\n            <image>tomcat:10-jre8-temurin-focal</image>\n          </from>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n          <container>\n            <appRoot>/usr/local/tomcat/webapps/ROOT</appRoot>\n          </container>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>com.test</groupId>\n  <artifactId>servlet25</artifactId>\n  <version>1</version>\n  <packaging>war</packaging>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <jib-maven-plugin.version>@@PluginVersion@@</jib-maven-plugin.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>jakarta.servlet</groupId>\n      <artifactId>jakarta.servlet-api</artifactId>\n      <version>5.0.0</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>  <!-- random dependency -->\n      <groupId>jakarta.annotation</groupId>\n      <artifactId>jakarta.annotation-api</artifactId>\n      <version>2.1.0</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>1.8</source>\n          <target>1.8</target>\n        </configuration>\n      </plugin>\n\n      <plugin>\n        <groupId>com.google.cloud.tools</groupId>\n        <artifactId>jib-maven-plugin</artifactId>\n        <version>${jib-maven-plugin.version}</version>\n        <configuration>\n          <to>\n            <image>${_TARGET_IMAGE}</image>\n          </to>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/extra_js/bogus.js",
    "content": "// nothing inside\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/extra_static/bogus.html",
    "content": "nothing inside\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/java/example/HelloWorld.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage example;\n\nimport jakarta.servlet.http.HttpServlet;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\npublic class HelloWorld extends HttpServlet {\n\n  @Override\n  public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {\n    try {\n      URL worldFile = getServletContext().getResource(\"/WEB-INF/classes/world\");\n      Path path = Paths.get(worldFile.toURI());\n      String world = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);\n\n      response.setContentType(\"text/plain\");\n      response.setCharacterEncoding(\"UTF-8\");\n\n      response.getWriter().print(\"Hello \" + world);\n\n    } catch (URISyntaxException e) {\n      throw new IOException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/resources/world",
    "content": "world"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/webapp/META-INF/MANIFEST.MF",
    "content": "Manifest-Version: 1.0\nClass-Path: \n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/webapp/WEB-INF/web.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Using the old Servlet API 2.5 for demonstration purposes. -->\n<web-app xmlns=\"http://java.sun.com/xml/ns/javaee\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd\"\n         version=\"2.5\">\n  <servlet>\n    <servlet-name>HelloWorld</servlet-name>\n    <servlet-class>example.HelloWorld</servlet-class>\n  </servlet>\n  <servlet-mapping>\n    <servlet-name>HelloWorld</servlet-name>\n    <url-pattern>/hello</url-pattern>\n  </servlet-mapping>\n</web-app>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/webapp/index.html",
    "content": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"content-type\" content=\"application/xhtml+xml; charset=UTF-8\" />\n    <title>Hello World</title>\n  </head>\n\n  <body>\n    <h1>Hello World!</h1>\n\n    <table>\n      <tr>\n        <td colspan=\"2\" style=\"font-weight:bold;\">Available Servlets:</td>\n      </tr>\n      <tr>\n        <td><a href='/hello'>The HelloWorld servlet</a></td>\n      </tr>\n    </table>\n  </body>\n</html>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/bad-encrypted-proxy-settings.xml",
    "content": "<settings>\n  <proxies>\n    <proxy>\n      <id>http</id>\n      <active>true</active>\n      <protocol>http</protocol>\n      <host>proxy.example.com</host>\n      <port>8080</port>\n      <username>proxyuser</username>\n      <password>{i-made-this-up=}</password>\n    </proxy>\n  </proxies>\n</settings>"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/encrypted-proxy-settings.xml",
    "content": "<settings>\n  <proxies>\n    <proxy>\n      <id>http</id>\n      <active>true</active>\n      <protocol>http</protocol>\n      <host>proxy1.example.com</host>\n      <port>8080</port>\n      <username>proxyuser</username>\n      <password>{AmwLphSL7qQHMPGxPn6NHQFNkU+ME6BU550hxoQrKHk=}</password> <!-- password1 -->\n    </proxy>\n    <proxy>\n      <id>https</id>\n      <active>true</active>\n      <protocol>https</protocol>\n      <host>proxy2.example.com</host>\n      <port>8443</port>\n      <username>proxyuser</username>\n      <password>password2</password>\n    </proxy>\n  </proxies>\n</settings>"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/http-only-proxy-settings.xml",
    "content": "<settings>\n  <proxies>\n    <proxy>\n      <id>http1</id>\n      <active>false</active>\n      <protocol>http</protocol>\n      <host>proxy1.example.com</host>\n      <port>8080</port>\n    </proxy>\n    <proxy>\n      <id>http2</id>\n      <active>true</active>\n      <protocol>http</protocol>\n      <host>proxy2.example.com</host>\n      <port>8080</port>\n    </proxy>\n    <proxy>\n      <id>http3</id>\n      <active>true</active>\n      <protocol>http</protocol>\n      <host>proxy3.example.com</host>\n      <port>8080</port>\n    </proxy>\n  </proxies>\n</settings>"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/https-only-proxy-settings.xml",
    "content": "<settings>\n  <proxies>\n    <proxy>\n      <id>https1</id>\n      <active>false</active>\n      <protocol>https</protocol>\n      <host>proxy1.example.com</host>\n      <port>8443</port>\n    </proxy>\n    <proxy>\n      <id>https2</id>\n      <active>true</active>\n      <protocol>https</protocol>\n      <host>proxy2.example.com</host>\n      <port>8443</port>\n    </proxy>\n    <proxy>\n      <id>https3</id>\n      <active>true</active>\n      <protocol>https</protocol>\n      <host>proxy3.example.com</host>\n      <port>8443</port>\n    </proxy>\n  </proxies>\n</settings>"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/no-active-proxy-settings.xml",
    "content": "<settings>\n  <proxies>\n    <proxy>\n      <id>http</id>\n      <active>false</active>\n      <protocol>http</protocol>\n      <host>proxy.example.com</host>\n      <port>8080</port>\n      <username>proxyuser</username>\n      <password>somepassword</password>\n    </proxy>\n    <proxy>\n      <id>https</id>\n      <active>false</active>\n      <protocol>https</protocol>\n      <host>proxy.example.com</host>\n      <port>8443</port>\n      <username>proxyuser</username>\n      <password>somepassword</password>\n    </proxy>\n  </proxies>\n</settings>"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/readme",
    "content": "Generate settings-security.xml with, if you change this, you have to change all the files in settings.xml, so just leave it as is:\n\n$ mvn --encrypt-master-password\nMaster password: <enter your password here>\n{generatedValue=}\n^^^ copy this password into security-settings.xml\n\nCreate settings.xml with mixed encrypted and unencrpted values, to encrypt a password value use:\n\n$ mvn --encrypt-password -Dsettings.security=<path to security-settings.xml>\nPassword: <enter password here>\n{generatedValue=}\n^^^ copy this password into the right spot in settings.xml\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/settings-security.empty.xml",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/settings-security.xml",
    "content": "<settingsSecurity>\n  <master>{}X6FMi5KzfGgHeRrZdTiwUdXfVSSqcNdXvVjt3tw7oxs=</master>\n</settingsSecurity>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/settings/settings.xml",
    "content": "<settings>\n  <servers>\n    <server>\n      <id>encryptedServer</id>\n      <username>encryptedUser</username>\n      <password>{AmwLphSL7qQHMPGxPn6NHQFNkU+ME6BU550hxoQrKHk=}</password> <!-- password1 -->\n    </server>\n    <server>\n      <id>simpleServer</id>\n      <username>simpleUser</username>\n      <password>password2</password>\n    </server>\n    <server>\n      <id>badServer</id>\n      <username>badUser</username>\n      <password>{i-made-this-up=}</password>\n    </server>\n    <server>\n      <id>docker.example.com</id>\n      <username>registryUser</username>\n      <password>registryPassword</password>\n    </server>\n  </servers>\n</settings>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependency/1.0.0/_remote.repositories",
    "content": "#NOTE: This is an Aether internal implementation file, its format can be changed without prior notice.\n#Wed Jul 11 18:33:40 EDT 2018\ndependency-1.0.0.pom>=\ndependency-1.0.0.jar>=\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependency/1.0.0/dependency-1.0.0.pom",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>com.test</groupId>\n  <artifactId>dependency</artifactId>\n  <version>1.0.0</version>\n  <description>POM was created from install:install-file</description>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependency/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n  <groupId>com.test</groupId>\n  <artifactId>dependency</artifactId>\n  <versioning>\n    <release>1.0.0</release>\n    <versions>\n      <version>1.0.0</version>\n    </versions>\n    <lastUpdated>20180711223340</lastUpdated>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependencyX/1.0.0-SNAPSHOT/_remote.repositories",
    "content": "#NOTE: This is an Aether internal implementation file, its format can be changed without prior notice.\n#Wed Jul 11 18:35:24 EDT 2018\ndependencyX-1.0.0-SNAPSHOT.pom>=\ndependencyX-1.0.0-SNAPSHOT.jar>=\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependencyX/1.0.0-SNAPSHOT/dependencyX-1.0.0-SNAPSHOT.pom",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>com.test</groupId>\n  <artifactId>dependencyX</artifactId>\n  <version>1.0.0-SNAPSHOT</version>\n  <description>POM was created from install:install-file</description>\n</project>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependencyX/1.0.0-SNAPSHOT/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata modelVersion=\"1.1.0\">\n  <groupId>com.test</groupId>\n  <artifactId>dependencyX</artifactId>\n  <version>1.0.0-SNAPSHOT</version>\n  <versioning>\n    <snapshot>\n      <localCopy>true</localCopy>\n    </snapshot>\n    <lastUpdated>20180711223524</lastUpdated>\n    <snapshotVersions>\n      <snapshotVersion>\n        <extension>jar</extension>\n        <value>1.0.0-SNAPSHOT</value>\n        <updated>20180711223524</updated>\n      </snapshotVersion>\n      <snapshotVersion>\n        <extension>pom</extension>\n        <value>1.0.0-SNAPSHOT</value>\n        <updated>20180711223524</updated>\n      </snapshotVersion>\n    </snapshotVersions>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/testM2/com/test/dependencyX/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n  <groupId>com.test</groupId>\n  <artifactId>dependencyX</artifactId>\n  <versioning>\n    <versions>\n      <version>1.0.0-SNAPSHOT</version>\n    </versions>\n    <lastUpdated>20180711223524</lastUpdated>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/webapp/final-name/META-INF/context.xml",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/webapp/final-name/Test.jsp",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/webapp/final-name/WEB-INF/classes/package/test.properties",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/maven/webapp/final-name/WEB-INF/web.xml",
    "content": ""
  },
  {
    "path": "jib-maven-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker",
    "content": "mock-maker-inline\n"
  },
  {
    "path": "jib-maven-plugin-extension-api/CHANGELOG.md",
    "content": "# Change Log\nAll notable changes to this project will be documented in this file.\n\n## [unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n## 0.4.0\n\n### Changed\n\n- Upgraded jib-build-plan to 0.4.0. ([#2660](https://github.com/GoogleContainerTools/jib/pull/2660))\n"
  },
  {
    "path": "jib-maven-plugin-extension-api/build.gradle",
    "content": "plugins {\n  id 'net.researchgate.release'\n  id 'maven-publish'\n  id 'eclipse'\n}\n\ndependencies {\n  api dependencyStrings.BUILD_PLAN\n  api dependencyStrings.EXTENSION_COMMON\n  api dependencyStrings.MAVEN_CORE\n}\n\njar {\n  manifest {\n    attributes 'Implementation-Version': archiveVersion\n    attributes 'Automatic-Module-Name': 'com.google.cloud.tools.jib.maven.extension'\n\n    // OSGi metadata\n    attributes 'Bundle-SymbolicName': 'com.google.cloud.tools.jib.maven.extension'\n    attributes 'Bundle-Name': 'Extension API for Jib Maven Plugin'\n    attributes 'Bundle-Vendor': 'Google LLC'\n    attributes 'Bundle-DocURL': 'https://github.com/GoogleContainerTools/jib'\n    attributes 'Bundle-License': 'https://www.apache.org/licenses/LICENSE-2.0'\n    attributes 'Export-Package': 'com.google.cloud.tools.jib.maven.extension'\n  }\n}\n\n/* RELEASE */\nconfigureMavenRelease()\n\npublishing {\n  publications {\n    mavenJava(MavenPublication) {\n      pom {\n        name = 'Extension API for Jib Maven Plugin'\n        description = 'Provides API to extend Jib Maven Plugin containerization.'\n      }\n      from components.java\n    }\n  }\n}\n\n// Release plugin (git release commits and version updates)\nrelease {\n  tagTemplate = 'v$version-maven-extension'\n  git {\n    requireBranch = /^maven-extension-release-v\\d+.*$/  //regex\n  }\n}\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-maven-plugin-extension-api/gradle.properties",
    "content": "version = 0.4.1-SNAPSHOT\n"
  },
  {
    "path": "jib-maven-plugin-extension-api/kokoro/release_build.sh",
    "content": "#!/bin/bash\n\n# Fail on any error.\nset -o errexit\n# Display commands to stderr.\nset -o xtrace\n\ncd github/jib\n./gradlew :jib-maven-plugin-extension-api:prepareRelease\n"
  },
  {
    "path": "jib-maven-plugin-extension-api/src/main/java/com/google/cloud/tools/jib/maven/extension/JibMavenPluginExtension.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.extension;\n\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtension;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport java.util.Map;\nimport java.util.Optional;\n\n/**\n * Jib Maven plugin extension API.\n *\n * <p>If a class implementing the interface is visible on the classpath of the Jib Maven plugin and\n * the plugin is configured to load the extension class, the Jib plugin extension framework calls\n * the interface method of the class.\n */\npublic interface JibMavenPluginExtension<T> extends JibPluginExtension {\n\n  /**\n   * The type of an custom configuration defined by this extension. The configuration object is\n   * mapped from {@code <pluginExtensions><pluginExtension><configuration>}. Often, it is sufficient\n   * to leverage {@code <pluginExtensions><pluginExtension><properties>} and the extension may not\n   * wish to define a custom configuration; in that case, use {@link Void} for &lt;T&gt; and have\n   * this method return {@code Optional#empty()}. (Don't return {@code Optional.of(Void.class)}.)\n   *\n   * @return type of an extension-specific custom configuration; {@code Optional.empty()} if no need\n   *     to define custom configuration\n   */\n  Optional<Class<T>> getExtraConfigType();\n\n  /**\n   * Extends the build plan prepared by the Jib Maven plugin.\n   *\n   * @param buildPlan original build plan prepared by the Jib Maven plugin\n   * @param properties custom properties configured for the plugin extension\n   * @param extraConfig extension-specific custom configuration mapped from {@code\n   *     <pluginExtensions><pluginExtension><configuration>} of type &lt;T&gt;. {@link\n   *     Optional#empty()} when {@link #getExtraConfigType()} returns {@link Optional#empty()} or\n   *     {@code <configuration>} is not specified by the extension user.\n   * @param mavenData {@link MavenData} providing Maven-specific data and properties\n   * @param logger logger for writing log messages\n   * @return updated build plan\n   * @throws JibPluginExtensionException if an error occurs while running the plugin extension\n   */\n  ContainerBuildPlan extendContainerBuildPlan(\n      ContainerBuildPlan buildPlan,\n      Map<String, String> properties,\n      Optional<T> extraConfig,\n      MavenData mavenData,\n      ExtensionLogger logger)\n      throws JibPluginExtensionException;\n}\n"
  },
  {
    "path": "jib-maven-plugin-extension-api/src/main/java/com/google/cloud/tools/jib/maven/extension/MavenData.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.maven.extension;\n\nimport org.apache.maven.execution.MavenSession;\nimport org.apache.maven.project.MavenProject;\n\n/** Holds Maven-specific data and properties. */\npublic interface MavenData {\n\n  MavenProject getMavenProject();\n\n  MavenSession getMavenSession();\n}\n"
  },
  {
    "path": "jib-plugins-common/README.md",
    "content": "Common code for Jib plugins. NOT intended for use outside of `jib-maven-plugin` or `jib-gradle-plugin` in its current state."
  },
  {
    "path": "jib-plugins-common/build.gradle",
    "content": "plugins {\n  id 'eclipse'\n}\n\ndependencies {\n  implementation project(':jib-core')\n  // since jib core doesn't export dependencies to a compile scope\n  // (they are \"runtime\"), just grab them manually. This means we don't have\n  // to synchronize dependencies between the two projects -- we don't\n  // want to use the sourceProject helper because it does things we don't want\n  // for an unpublished library.\n  implementation project(':jib-core').configurations.implementation.dependencies\n\n  implementation dependencyStrings.EXTENSION_COMMON\n\n  testImplementation dependencyStrings.JUNIT\n  testImplementation dependencyStrings.JUNIT_PARAMS\n  testImplementation dependencyStrings.TRUTH\n  testImplementation dependencyStrings.TRUTH8\n  testImplementation dependencyStrings.MOCKITO_CORE\n  testImplementation dependencyStrings.SLF4J_API\n  testImplementation dependencyStrings.SYSTEM_RULES\n  testImplementation project(path:':jib-core', configuration:'tests')\n}\n\nsourceSets.test.resources.srcDirs project(':jib-core').sourceSets.test.resources\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-plugins-common/gradle.properties",
    "content": "version = 0.1.0-SNAPSHOT\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/AuthProperty.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport javax.annotation.Nullable;\n\n/** Holds a username and password property. */\npublic interface AuthProperty {\n\n  @Nullable\n  String getUsername();\n\n  @Nullable\n  String getPassword();\n\n  String getAuthDescriptor();\n\n  String getUsernameDescriptor();\n\n  String getPasswordDescriptor();\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/BuildStepsExecutionException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/** Wraps an exception that happens during containerization. */\npublic class BuildStepsExecutionException extends Exception {\n\n  BuildStepsExecutionException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidator.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.http.Authorization;\nimport com.google.common.base.Strings;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.annotation.Nullable;\n\n/** Validator for plugin configuration parameters and system properties. */\npublic class ConfigurationPropertyValidator {\n\n  /** Matches key-value pairs in the form of \"key=value\". */\n  private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(\"(?<name>[^=]+)=(?<value>.*)\");\n\n  /**\n   * Gets a {@link Credential} from a username and password. First tries system properties, then\n   * tries build configuration, otherwise returns null.\n   *\n   * @param logger a consumer for handling log events\n   * @param usernameProperty the name of the username system property\n   * @param passwordProperty the name of the password system property\n   * @param auth the configured credentials\n   * @param rawConfiguration the {@link RawConfiguration} that provides system properties\n   * @return a new {@link Authorization} from the system properties or build configuration, or\n   *     {@link Optional#empty} if neither is configured.\n   */\n  public static Optional<Credential> getImageCredential(\n      Consumer<LogEvent> logger,\n      String usernameProperty,\n      String passwordProperty,\n      AuthProperty auth,\n      RawConfiguration rawConfiguration) {\n    // System property takes priority over build configuration\n    String commandlineUsername = rawConfiguration.getProperty(usernameProperty).orElse(\"\");\n    String commandlinePassword = rawConfiguration.getProperty(passwordProperty).orElse(\"\");\n    if (!commandlineUsername.isEmpty() && !commandlinePassword.isEmpty()) {\n      return Optional.of(Credential.from(commandlineUsername, commandlinePassword));\n    }\n\n    // Warn if a system property is missing\n    String missingProperty =\n        \"%s system property is set, but %s is not; attempting other authentication methods.\";\n    if (!commandlinePassword.isEmpty()) {\n      logger.accept(\n          LogEvent.warn(String.format(missingProperty, passwordProperty, usernameProperty)));\n    }\n    if (!commandlineUsername.isEmpty()) {\n      logger.accept(\n          LogEvent.warn(String.format(missingProperty, usernameProperty, passwordProperty)));\n    }\n\n    // Check auth configuration next; warn if they aren't both set\n    if (!Strings.isNullOrEmpty(auth.getUsername()) && !Strings.isNullOrEmpty(auth.getPassword())) {\n      return Optional.of(Credential.from(auth.getUsername(), auth.getPassword()));\n    }\n\n    String missingConfig = \"%s is missing from build configuration; ignoring auth section.\";\n    if (!Strings.isNullOrEmpty(auth.getPassword())) {\n      logger.accept(LogEvent.warn(String.format(missingConfig, auth.getUsernameDescriptor())));\n    }\n    if (!Strings.isNullOrEmpty(auth.getUsername())) {\n      logger.accept(LogEvent.warn(String.format(missingConfig, auth.getPasswordDescriptor())));\n    }\n\n    return Optional.empty();\n  }\n\n  /**\n   * Returns an {@link ImageReference} parsed from the configured target image, or one of the form\n   * {@code project-name:project-version} if target image is not configured.\n   *\n   * @param targetImage the configured target image reference\n   * @param projectProperties the {@link ProjectProperties} providing the project name, version, and\n   *     log event handler\n   * @param helpfulSuggestions used for generating the message notifying the user of the generated\n   *     tag\n   * @return an {@link ImageReference} parsed from the configured target image, or one of the form\n   *     {@code project-name:project-version} if target image is not configured\n   * @throws InvalidImageReferenceException if the configured or generated image reference is\n   *     invalid\n   */\n  public static ImageReference getGeneratedTargetDockerTag(\n      @Nullable String targetImage,\n      ProjectProperties projectProperties,\n      HelpfulSuggestions helpfulSuggestions)\n      throws InvalidImageReferenceException {\n    String generatedName = projectProperties.getName();\n    String generatedTag =\n        projectProperties.getVersion().equals(\"unspecified\")\n            ? \"latest\"\n            : projectProperties.getVersion();\n    if (Strings.isNullOrEmpty(targetImage)) {\n      projectProperties.log(\n          LogEvent.lifecycle(helpfulSuggestions.forGeneratedTag(generatedName, generatedTag)));\n\n      // Try to parse generated tag to verify that project name and version are valid (throws an\n      // exception if parse fails)\n      ImageReference.parse(generatedName + \":\" + generatedTag);\n      return ImageReference.of(null, generatedName, generatedTag);\n    } else {\n      return ImageReference.parse(targetImage);\n    }\n  }\n\n  /**\n   * Parses a string in the form of \"key1=value1,key2=value2,...\" into a map.\n   *\n   * @param property the map string to parse, with entries separated by \",\" and key-value pairs\n   *     separated by \"=\"\n   * @return the map of parsed values\n   */\n  public static Map<String, String> parseMapProperty(String property) {\n    Map<String, String> result = new LinkedHashMap<>(); // LinkedHashMap to keep insertion order\n\n    // Split on non-escaped commas\n    List<String> entries = parseListProperty(property);\n    for (String entry : entries) {\n      Matcher matcher = KEY_VALUE_PATTERN.matcher(entry);\n      if (!matcher.matches()) {\n        throw new IllegalArgumentException(\"'\" + entry + \"' is not a valid key-value pair\");\n      }\n      result.put(matcher.group(\"name\"), matcher.group(\"value\"));\n    }\n    return result;\n  }\n\n  /**\n   * Parses a comma-separated string into a list. Ignores commas escaped with \"\\\".\n   *\n   * @param property the comma-separated string\n   * @return the list of parsed values\n   */\n  public static List<String> parseListProperty(String property) {\n    List<String> items = new ArrayList<>();\n    StringBuilder token = new StringBuilder();\n    for (int i = 0; i < property.length(); i++) {\n      if (property.charAt(i) == ',') {\n        // Split on non-escaped comma\n        items.add(token.toString());\n        token.setLength(0);\n      } else {\n        if (i + 1 < property.length()\n            && property.charAt(i) == '\\\\'\n            && property.charAt(i + 1) == ',') {\n          // Found an escaped comma. Add a comma.\n          i++;\n        }\n        token.append(property.charAt(i));\n      }\n    }\n    items.add(token.toString());\n    return items;\n  }\n\n  private ConfigurationPropertyValidator() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ContainerizingMode.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport java.util.Locale;\n\n/**\n * Containerizing mode.\n *\n * <ul>\n *   <li>{@code EXPLODED} puts individual application files without packaging.\n *   <li>{@code PACKAGED} puts a single packaged artifact for an application.\n * </ul>\n */\npublic enum ContainerizingMode {\n  EXPLODED,\n  PACKAGED;\n\n  /**\n   * Converts a string representation of ContainerizingMode to Enum. It requires an all lowercase\n   * string that matches the enum value exactly.\n   *\n   * @param rawMode the raw string to parse\n   * @return the enum equivalent of the mode\n   * @throws InvalidContainerizingModeException when not lowercase, or cannot match to an values of\n   *     this enum class\n   */\n  public static ContainerizingMode from(String rawMode) throws InvalidContainerizingModeException {\n    try {\n      if (!rawMode.toLowerCase(Locale.US).equals(rawMode)) {\n        throw new InvalidContainerizingModeException(rawMode, rawMode);\n      }\n      return ContainerizingMode.valueOf(rawMode.toUpperCase(Locale.US));\n    } catch (IllegalArgumentException ex) {\n      throw new InvalidContainerizingModeException(rawMode, rawMode);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrievers.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.FileNotFoundException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport javax.annotation.Nullable;\n\n/**\n * Generates a list of default {@link CredentialRetriever}s.\n *\n * <p>The retrievers are, in order of first-checked to last-checked:\n *\n * <ol>\n *   <li>{@link CredentialRetrieverFactory#known} for known credential, if set\n *   <li>{@link CredentialRetrieverFactory#dockerCredentialHelper} for a known credential helper, if\n *       set\n *   <li>{@link CredentialRetrieverFactory#dockerConfig} for {@code\n *       $XDG_RUNTIME_DIR/containers/auth.json},\n *   <li>{@link CredentialRetrieverFactory#dockerConfig} for {@code\n *       $XDG_CONFIG_HOME/containers/auth.json},\n *   <li>{@link CredentialRetrieverFactory#dockerConfig} for {@code\n *       $HOME/.config/containers/auth.json},\n *   <li>{@link CredentialRetrieverFactory#known} for known inferred credential, if set\n *   <li>{@link CredentialRetrieverFactory#dockerConfig} for {@code $DOCKER_CONFIG/config.json},\n *       {@code $DOCKER_CONFIG/.dockerconfigjson}, {@code $DOCKER_CONFIG/.dockercfg},\n *       System.get(\"user.home\")/.docker/config.json}, {@code\n *       System.get(\"user.home\")/.docker/.dockerconfigjson}, {@code\n *       System.get(\"user.home\")/.docker/.dockercfg}, {@code $HOME/.docker/config.json}, {@code\n *       $HOME/.docker/.dockerconfigjson}, and {@code $HOME/.docker/.dockercfg}\n *   <li>{@link CredentialRetrieverFactory#wellKnownCredentialHelpers} for well-known credential\n *       helper-registry pairs\n *   <li>{@link CredentialRetrieverFactory#googleApplicationDefaultCredentials} for GCR registry\n * </ol>\n */\npublic class DefaultCredentialRetrievers {\n\n  /**\n   * See <a\n   * href=\"https://docs.docker.com/engine/reference/commandline/login/#privileged-user-requirement\">https://docs.docker.com/engine/reference/commandline/login/#privileged-user-requirement</a>.\n   */\n  private static final Path DOCKER_CONFIG_FILE = Paths.get(\"config.json\");\n  // for Kubernetes: https://github.com/GoogleContainerTools/jib/issues/2260\n  private static final Path KUBERNETES_DOCKER_CONFIG_FILE = Paths.get(\".dockerconfigjson\");\n  private static final Path LEGACY_DOCKER_CONFIG_FILE = Paths.get(\".dockercfg\");\n  // for Podman: https://www.mankier.com/5/containers-auth.json\n  private static final Path XDG_AUTH_FILE = Paths.get(\"containers\").resolve(\"auth.json\");\n\n  /**\n   * Creates a new {@link DefaultCredentialRetrievers} with a given {@link\n   * CredentialRetrieverFactory}.\n   *\n   * @param credentialRetrieverFactory the {@link CredentialRetrieverFactory} to generate the {@link\n   *     CredentialRetriever}s\n   * @return a new {@link DefaultCredentialRetrievers}\n   */\n  public static DefaultCredentialRetrievers init(\n      CredentialRetrieverFactory credentialRetrieverFactory) {\n    return new DefaultCredentialRetrievers(\n        credentialRetrieverFactory, System.getProperties(), System.getenv());\n  }\n\n  private final CredentialRetrieverFactory credentialRetrieverFactory;\n\n  @Nullable private CredentialRetriever knownCredentialRetriever;\n  @Nullable private CredentialRetriever inferredCredentialRetriever;\n  @Nullable private String credentialHelper;\n  private final Properties systemProperties;\n  private final Map<String, String> environment;\n\n  @VisibleForTesting\n  DefaultCredentialRetrievers(\n      CredentialRetrieverFactory credentialRetrieverFactory,\n      Properties systemProperties,\n      Map<String, String> environment) {\n    this.credentialRetrieverFactory = credentialRetrieverFactory;\n    this.systemProperties = systemProperties;\n    this.environment = environment;\n  }\n\n  /**\n   * Sets the known {@link Credential} to use in the default credential retrievers.\n   *\n   * @param knownCredential the known credential\n   * @param credentialSource the source of the known credential (for logging)\n   * @return this\n   */\n  public DefaultCredentialRetrievers setKnownCredential(\n      Credential knownCredential, String credentialSource) {\n    knownCredentialRetriever = credentialRetrieverFactory.known(knownCredential, credentialSource);\n    return this;\n  }\n\n  /**\n   * Sets the inferred {@link Credential} to use in the default credential retrievers.\n   *\n   * @param inferredCredential the inferred credential\n   * @param credentialSource the source of the inferred credential (for logging)\n   * @return this\n   */\n  public DefaultCredentialRetrievers setInferredCredential(\n      Credential inferredCredential, String credentialSource) {\n    inferredCredentialRetriever =\n        credentialRetrieverFactory.known(inferredCredential, credentialSource);\n    return this;\n  }\n\n  /**\n   * Sets the known credential helper. May either be a path to a credential helper executable, or a\n   * credential helper suffix (following {@code docker-credential-}).\n   *\n   * @param credentialHelper the path to a credential helper, or a credential helper suffix\n   *     (following {@code docker-credential-}).\n   * @return this\n   */\n  public DefaultCredentialRetrievers setCredentialHelper(@Nullable String credentialHelper) {\n    this.credentialHelper = credentialHelper;\n    return this;\n  }\n\n  /**\n   * Makes a list of {@link CredentialRetriever}s.\n   *\n   * @return the list of {@link CredentialRetriever}s\n   * @throws FileNotFoundException if a credential helper path is specified, but the file doesn't\n   *     exist\n   */\n  public List<CredentialRetriever> asList() throws FileNotFoundException {\n    List<CredentialRetriever> credentialRetrievers = new ArrayList<>();\n    if (knownCredentialRetriever != null) {\n      credentialRetrievers.add(knownCredentialRetriever);\n    }\n    if (credentialHelper != null) {\n      // If credential helper contains file separator, treat as path; otherwise treat as suffix\n      if (credentialHelper.contains(FileSystems.getDefault().getSeparator())) {\n        if (!Files.exists(Paths.get(credentialHelper))) {\n          String osName = systemProperties.getProperty(\"os.name\").toLowerCase(Locale.ENGLISH);\n          if (!osName.contains(\"windows\")\n              || (!Files.exists(Paths.get(credentialHelper + \".cmd\"))\n                  && !Files.exists(Paths.get(credentialHelper + \".exe\")))) {\n            throw new FileNotFoundException(\n                \"Specified credential helper was not found: \" + credentialHelper);\n          }\n        }\n        credentialRetrievers.add(\n            credentialRetrieverFactory.dockerCredentialHelper(credentialHelper));\n      } else {\n        String suffix = credentialHelper; // not path; treat as suffix\n        credentialRetrievers.add(\n            credentialRetrieverFactory.dockerCredentialHelper(\"docker-credential-\" + suffix));\n      }\n    }\n\n    if (inferredCredentialRetriever != null) {\n      credentialRetrievers.add(inferredCredentialRetriever);\n    }\n\n    Set<Path> dockerConfigFiles = new LinkedHashSet<>();\n\n    String xdgRuntime = environment.get(\"XDG_RUNTIME_DIR\");\n    if (xdgRuntime != null) {\n      dockerConfigFiles.add(Paths.get(xdgRuntime).resolve(XDG_AUTH_FILE));\n    }\n    String xdgConfigHome = environment.get(\"XDG_CONFIG_HOME\");\n    if (xdgConfigHome != null) {\n      dockerConfigFiles.add(Paths.get(xdgConfigHome).resolve(XDG_AUTH_FILE));\n    }\n    String homeProperty = systemProperties.getProperty(\"user.home\");\n    if (homeProperty != null) {\n      dockerConfigFiles.add(Paths.get(homeProperty).resolve(\".config\").resolve(XDG_AUTH_FILE));\n    }\n    String homeEnvVar = environment.get(\"HOME\");\n    if (homeEnvVar != null) {\n      dockerConfigFiles.add(Paths.get(homeEnvVar).resolve(\".config\").resolve(XDG_AUTH_FILE));\n    }\n\n    String dockerConfigEnv = environment.get(\"DOCKER_CONFIG\");\n    if (dockerConfigEnv != null) {\n      dockerConfigFiles.addAll(getDockerFiles(Paths.get(dockerConfigEnv)));\n    }\n    if (homeProperty != null) {\n      dockerConfigFiles.addAll(getDockerFiles(Paths.get(homeProperty).resolve(\".docker\")));\n    }\n    if (homeEnvVar != null) {\n      dockerConfigFiles.addAll(getDockerFiles(Paths.get(homeEnvVar).resolve(\".docker\")));\n    }\n\n    dockerConfigFiles.stream()\n        .map(\n            path ->\n                path.endsWith(LEGACY_DOCKER_CONFIG_FILE)\n                    ? credentialRetrieverFactory.legacyDockerConfig(path)\n                    : credentialRetrieverFactory.dockerConfig(path))\n        .forEach(credentialRetrievers::add);\n\n    credentialRetrievers.add(credentialRetrieverFactory.wellKnownCredentialHelpers());\n    credentialRetrievers.add(credentialRetrieverFactory.googleApplicationDefaultCredentials());\n    return credentialRetrievers;\n  }\n\n  private List<Path> getDockerFiles(Path configDir) {\n    return Arrays.asList(\n        configDir.resolve(DOCKER_CONFIG_FILE),\n        configDir.resolve(KUBERNETES_DOCKER_CONFIG_FILE),\n        configDir.resolve(LEGACY_DOCKER_CONFIG_FILE));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ExtraDirectoryNotFoundException.java",
    "content": "/*\n * Copyright 2022 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/** Indicates that the {@code extraDirectories.paths.path} is not found. */\npublic class ExtraDirectoryNotFoundException extends Exception {\n\n  private final String path;\n\n  public ExtraDirectoryNotFoundException(String message, String path) {\n    super(message);\n    this.path = path;\n  }\n\n  public String getPath() {\n    return path;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/HelpfulSuggestions.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport java.nio.file.Path;\n\n/** Builds messages that provides suggestions on how to fix the error. */\npublic class HelpfulSuggestions {\n\n  /**\n   * Generates message for when \"target image\" isn't configured.\n   *\n   * @param messagePrefix the initial message text\n   * @param parameter the parameter name (e.g. 'to.image' or {@literal <to><image>})\n   * @param buildConfigFilename the name of the build config (build.gradle or pom.xml)\n   * @param command an example command for passing the parameter via commandline\n   * @return a suggested fix for a missing target image configuration\n   */\n  public static String forToNotConfigured(\n      String messagePrefix, String parameter, String buildConfigFilename, String command) {\n    return suggest(\n        messagePrefix,\n        \"add a \"\n            + parameter\n            + \" configuration parameter to your \"\n            + buildConfigFilename\n            + \" or set the parameter via the commandline (e.g. '\"\n            + command\n            + \"').\");\n  }\n\n  public static String forDockerNotInstalled(String messagePrefix) {\n    return suggest(\n        messagePrefix, \"make sure Docker is installed and you have correct privileges to run it\");\n  }\n\n  public static String forMainClassNotFound(String messagePrefix, String pluginName) {\n    return suggest(messagePrefix, \"add a `mainClass` configuration to \" + pluginName);\n  }\n\n  public static String forIncompatibleBaseImageJavaVersionForGradle(\n      int baseImageMajorJavaVersion, int projectMajorJavaVersion) {\n    return forIncompatibleBaseImageJavaVersion(\n        baseImageMajorJavaVersion,\n        projectMajorJavaVersion,\n        \"using the 'jib.from.image' parameter, or set targetCompatibility = \"\n            + baseImageMajorJavaVersion\n            + \" or below\");\n  }\n\n  public static String forIncompatibleBaseImageJavaVersionForMaven(\n      int baseImageMajorJavaVersion, int projectMajorJavaVersion) {\n    return forIncompatibleBaseImageJavaVersion(\n        baseImageMajorJavaVersion,\n        projectMajorJavaVersion,\n        \"using the '<from><image>' parameter, or set maven-compiler-plugin's '<target>' or \"\n            + \"'<release>' version to \"\n            + baseImageMajorJavaVersion\n            + \" or below\");\n  }\n\n  public static String forInvalidImageReference(String reference) {\n    return suggest(\n        \"Invalid image reference \" + reference,\n        \"check that the reference is formatted correctly according to \"\n            + \"https://docs.docker.com/engine/reference/commandline/tag/#extended-description\\n\"\n            + \"For example, slash-separated name components cannot have uppercase letters\");\n  }\n\n  /**\n   * Helper for creating messages with suggestions.\n   *\n   * @param messagePrefix the initial message text\n   * @param suggestion a suggested fix for the problem described by param: messagePrefix\n   * @return the message containing the suggestion\n   */\n  public static String suggest(String messagePrefix, String suggestion) {\n    return messagePrefix + \", perhaps you should \" + suggestion;\n  }\n\n  private static String forIncompatibleBaseImageJavaVersion(\n      int baseImageMajorJavaVersion, int projectMajorJavaVersion, String parameterInstructions) {\n    return suggest(\n        \"Your project is using Java \"\n            + projectMajorJavaVersion\n            + \" but the base image is for Java \"\n            + baseImageMajorJavaVersion,\n        \"configure a Java \"\n            + projectMajorJavaVersion\n            + \"-compatible base image \"\n            + parameterInstructions\n            + \" in your build configuration\");\n  }\n\n  private final String messagePrefix;\n  private final String clearCacheCommand;\n  private final String toImageConfiguration;\n  private final String buildConfigurationFilename;\n  private final String toImageFlag;\n\n  /**\n   * Creates a new {@link HelpfulSuggestions} with frontend-specific texts.\n   *\n   * @param messagePrefix the initial message text\n   * @param clearCacheCommand the command for clearing the cache\n   * @param toImageConfiguration the configuration defining the target image\n   * @param toImageFlag the commandline flag used to set the target image\n   * @param buildConfigurationFilename the filename of the build configuration\n   */\n  public HelpfulSuggestions(\n      String messagePrefix,\n      String clearCacheCommand,\n      String toImageConfiguration,\n      String toImageFlag,\n      String buildConfigurationFilename) {\n    this.messagePrefix = messagePrefix;\n    this.clearCacheCommand = clearCacheCommand;\n    this.toImageConfiguration = toImageConfiguration;\n    this.buildConfigurationFilename = buildConfigurationFilename;\n    this.toImageFlag = toImageFlag;\n  }\n\n  public String forHttpHostConnect() {\n    return suggest(\"make sure your Internet is up and that the registry you are pushing to exists\");\n  }\n\n  public String forUnknownHost() {\n    return suggest(\"make sure that the registry you configured exists/is spelled properly\");\n  }\n\n  public String forCacheNeedsClean() {\n    return suggest(\"run '\" + clearCacheCommand + \"' to clear your build cache\");\n  }\n\n  public String forCacheDirectoryNotOwned(Path cacheDirectory) {\n    return suggest(\n        \"check that '\"\n            + cacheDirectory\n            + \"' is not used by another application or set the `jib.useOnlyProjectCache` system \"\n            + \"property\");\n  }\n\n  public String forHttpStatusCodeForbidden(String imageReference) {\n    return suggest(\n        \"make sure you have permissions for \"\n            + imageReference\n            + \" and set correct credentials. See \"\n            + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-forbidden-or-denied for help\");\n  }\n\n  public String forNoCredentialsDefined(String imageReference) {\n    return suggest(\n        \"make sure your credentials for '\"\n            + imageReference\n            + \"' are set up correctly. See \"\n            + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-unauthorized for help\");\n  }\n\n  public String forCredentialsNotSent() {\n    return suggest(\n        \"use a registry that supports HTTPS so credentials can be sent safely, or set the 'sendCredentialsOverHttp' system property to true\");\n  }\n\n  public String forInsecureRegistry() {\n    return suggest(\n        \"use a registry that supports HTTPS or set the configuration parameter 'allowInsecureRegistries'\");\n  }\n\n  public String forGeneratedTag(String projectName, String projectVersion) {\n    return \"Tagging image with generated image reference \"\n        + projectName\n        + \":\"\n        + projectVersion\n        + \". If you'd like to specify a different tag, you can set the \"\n        + toImageConfiguration\n        + \" parameter in your \"\n        + buildConfigurationFilename\n        + \", or use the \"\n        + toImageFlag\n        + \"=<MY IMAGE> commandline flag.\";\n  }\n\n  public String none() {\n    return messagePrefix;\n  }\n\n  /**\n   * Helper for suggestions with configured message prefix.\n   *\n   * @param suggestion a suggested fix for the problem described by {@link #messagePrefix}\n   * @return the message containing the suggestion\n   */\n  public String suggest(String suggestion) {\n    return suggest(messagePrefix, suggestion);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * Builds a JSON string containing metadata about a {@link JibContainer} from a build.\n *\n * <p>Example:\n *\n * <pre>{@code\n * {\n *   \"image\": \"gcr.io/project/image:tag\",\n *   \"imageId\": \"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\",\n *   \"imageDigest\": \"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6f\",\n *   \"tags\": [\"latest\", \"tag\"]\n * }\n * }</pre>\n */\npublic class ImageMetadataOutput implements JsonTemplate {\n\n  private final String image;\n  private final String imageId;\n  private final String imageDigest;\n  private final List<String> tags;\n  private final Boolean imagePushed;\n\n  @JsonCreator\n  ImageMetadataOutput(\n      @JsonProperty(value = \"image\", required = true) String image,\n      @JsonProperty(value = \"imageId\", required = true) String imageId,\n      @JsonProperty(value = \"imageDigest\", required = true) String imageDigest,\n      @JsonProperty(value = \"tags\", required = true) List<String> tags,\n      @JsonProperty(value = \"imagePushed\", required = true) Boolean imagePushed) {\n    this.image = image;\n    this.imageId = imageId;\n    this.imageDigest = imageDigest;\n    this.tags = tags;\n    this.imagePushed = imagePushed;\n  }\n\n  @VisibleForTesting\n  static ImageMetadataOutput fromJson(String json) throws IOException {\n    return JsonTemplateMapper.readJson(json, ImageMetadataOutput.class);\n  }\n\n  /**\n   * Create reproducible image build metadata from {@link JibContainer} information.\n   *\n   * @param jibContainer the metadata source\n   * @return a json template populated with image metadata\n   */\n  public static ImageMetadataOutput fromJibContainer(JibContainer jibContainer) {\n    String image = jibContainer.getTargetImage().toString();\n    String imageId = jibContainer.getImageId().toString();\n    String imageDigest = jibContainer.getDigest().toString();\n    Boolean imagePushed = jibContainer.isImagePushed();\n\n    // Make sure tags always appear in a predictable way, by sorting them into a list\n    List<String> tags = ImmutableList.sortedCopyOf(jibContainer.getTags());\n\n    return new ImageMetadataOutput(image, imageId, imageDigest, tags, imagePushed);\n  }\n\n  public String getImage() {\n    return image;\n  }\n\n  public String getImageId() {\n    return imageId;\n  }\n\n  public String getImageDigest() {\n    return imageDigest;\n  }\n\n  public List<String> getTags() {\n    return tags;\n  }\n\n  public Boolean isImagePushed() {\n    return imagePushed;\n  }\n\n  public String toJson() throws IOException {\n    return JsonTemplateMapper.toUtf8String(this);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/IncompatibleBaseImageJavaVersionException.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/**\n * Exception when the Java version in the base image is incompatible with the Java version of the\n * application to be containerized. For example, when the project is Java 11 but the base image is\n * Java 8.\n */\npublic class IncompatibleBaseImageJavaVersionException extends Exception {\n\n  private final int baseImageMajorJavaVersion;\n  private final int projectMajorJavaVersion;\n\n  public IncompatibleBaseImageJavaVersionException(\n      int baseImageMajorJavaVersion, int projectMajorJavaVersion) {\n    this.baseImageMajorJavaVersion = baseImageMajorJavaVersion;\n    this.projectMajorJavaVersion = projectMajorJavaVersion;\n  }\n\n  public int getBaseImageMajorJavaVersion() {\n    return baseImageMajorJavaVersion;\n  }\n\n  public int getProjectMajorJavaVersion() {\n    return projectMajorJavaVersion;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InferredAuthException.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/**\n * Indicates that the {@link InferredAuthProvider} encountered a failure while trying to determine\n * auth credentials (not thrown for missing).\n */\npublic class InferredAuthException extends Exception {\n  public InferredAuthException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InferredAuthProvider.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport java.util.Optional;\n\n/** An auth provider specific to the client's architecture. */\npublic interface InferredAuthProvider {\n\n  /**\n   * Find auth credentials for a specific registry.\n   *\n   * @param registry we want credential for\n   * @return auth information for the registry (can be empty)\n   * @throws InferredAuthException if the auth discovery process resulted in a fatal error\n   */\n  Optional<AuthProperty> inferAuth(String registry) throws InferredAuthException;\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidAppRootException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/**\n * Indicates that the {@code container.appRoot} config value is invalid. (The path is not in the\n * absolute unix-path style.\n */\npublic class InvalidAppRootException extends Exception {\n\n  private final String invalidAppRoot;\n\n  public InvalidAppRootException(String message, String invalidAppRoot, Throwable ex) {\n    super(message, ex);\n    this.invalidAppRoot = invalidAppRoot;\n  }\n\n  public String getInvalidPathValue() {\n    return invalidAppRoot;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidContainerVolumeException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/**\n * Indicates that the {@code container.volumes} config value has at least one invalid path. (The\n * path is not in the absolute unix-path style).\n */\npublic class InvalidContainerVolumeException extends Exception {\n\n  private String invalidVolume;\n\n  InvalidContainerVolumeException(String message, String invalidVolume, Throwable cause) {\n    super(message, cause);\n    this.invalidVolume = invalidVolume;\n  }\n\n  public String getInvalidVolume() {\n    return invalidVolume;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidContainerizingModeException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/** Indicates that the {@code containerizingMode} config value is invalid. */\npublic class InvalidContainerizingModeException extends Exception {\n\n  private final String invalidContainerizingMode;\n\n  public InvalidContainerizingModeException(String message, String invalidContainerizingMode) {\n    super(message);\n    this.invalidContainerizingMode = invalidContainerizingMode;\n  }\n\n  public String getInvalidContainerizingMode() {\n    return invalidContainerizingMode;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidCreationTimeException.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport java.time.format.DateTimeParseException;\n\n/** Exception when an invalid container creation timestamp configuration is encountered. */\npublic class InvalidCreationTimeException extends Exception {\n\n  private final String invalidValue;\n\n  public InvalidCreationTimeException(\n      String message, String invalidValue, DateTimeParseException ex) {\n    super(message, ex);\n    this.invalidValue = invalidValue;\n  }\n\n  public String getInvalidCreationTime() {\n    return invalidValue;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidFilesModificationTimeException.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport java.time.format.DateTimeParseException;\n\n/** Exception when an invalid file timestamp configuration is encountered. */\npublic class InvalidFilesModificationTimeException extends Exception {\n\n  private final String invalidValue;\n\n  public InvalidFilesModificationTimeException(\n      String message, String invalidValue, DateTimeParseException ex) {\n    super(message, ex);\n    this.invalidValue = invalidValue;\n  }\n\n  public String getInvalidFilesModificationTime() {\n    return invalidValue;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidPlatformException.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/**\n * Indicates that the {@code from.platforms} config value has at least one invalid platform. (For\n * example, a platform misses a required field or has an invalid value.)\n */\npublic class InvalidPlatformException extends Exception {\n\n  private final String platform;\n\n  InvalidPlatformException(String message, String platform) {\n    super(message);\n    this.platform = platform;\n  }\n\n  public String getInvalidPlatform() {\n    return platform;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/InvalidWorkingDirectoryException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/**\n * Indicates that the {@code container.workingDirectory} config value is invalid. (The path is not\n * in the absolute unix-path style).\n */\npublic class InvalidWorkingDirectoryException extends Exception {\n\n  private final String invalidPath;\n\n  public InvalidWorkingDirectoryException(String message, String invalidPath, Throwable ex) {\n    super(message, ex);\n    this.invalidPath = invalidPath;\n  }\n\n  public String getInvalidPathValue() {\n    return invalidPath;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelper.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.ModificationTimeProvider;\nimport com.google.cloud.tools.jib.api.buildplan.RelativeUnixPath;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport java.io.IOException;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.PathMatcher;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Predicate;\n\n/** Helper for constructing {@link JavaContainerBuilder}-based {@link JibContainerBuilder}s. */\npublic class JavaContainerBuilderHelper {\n\n  private static final String GLOB_PREFIX = \"glob:\";\n\n  /**\n   * Returns a {@link FileEntriesLayer} for adding the extra directory to the container.\n   *\n   * @param sourceDirectory the source extra directory path\n   * @param targetDirectory the root directory on the container to place the files in\n   * @param includes the list of glob patterns to include from the source directory\n   * @param excludes the list of glob patterns to exclude from the source directory\n   * @param extraDirectoryPermissions map from path on container to file permissions\n   * @param modificationTimeProvider file modification time provider\n   * @return a {@link FileEntriesLayer} for adding the extra directory to the container\n   * @throws IOException if walking the extra directory fails\n   */\n  public static FileEntriesLayer extraDirectoryLayerConfiguration(\n      Path sourceDirectory,\n      AbsoluteUnixPath targetDirectory,\n      List<String> includes,\n      List<String> excludes,\n      Map<String, FilePermissions> extraDirectoryPermissions,\n      ModificationTimeProvider modificationTimeProvider)\n      throws IOException {\n    FileEntriesLayer.Builder builder =\n        FileEntriesLayer.builder().setName(LayerType.EXTRA_FILES.getName());\n    Map<PathMatcher, FilePermissions> permissionsPathMatchers = new LinkedHashMap<>();\n    for (Map.Entry<String, FilePermissions> entry : extraDirectoryPermissions.entrySet()) {\n      permissionsPathMatchers.put(\n          FileSystems.getDefault().getPathMatcher(GLOB_PREFIX + entry.getKey()), entry.getValue());\n    }\n\n    DirectoryWalker walker = new DirectoryWalker(sourceDirectory).filterRoot();\n    // add exclusion filters\n    excludes.stream()\n        .map(pattern -> FileSystems.getDefault().getPathMatcher(GLOB_PREFIX + pattern))\n        .forEach(\n            pathMatcher ->\n                walker.filter(path -> !pathMatcher.matches(sourceDirectory.relativize(path))));\n    // add an inclusion filter\n    includes.stream()\n        .map(pattern -> FileSystems.getDefault().getPathMatcher(GLOB_PREFIX + pattern))\n        .map(\n            pathMatcher ->\n                (Predicate<Path>) path -> pathMatcher.matches(sourceDirectory.relativize(path)))\n        .reduce((matches1, matches2) -> matches1.or(matches2))\n        .ifPresent(walker::filter);\n    // walk the source tree and add layer entries\n    walker.walk(\n        localPath -> {\n          AbsoluteUnixPath pathOnContainer =\n              targetDirectory.resolve(sourceDirectory.relativize(localPath));\n          Instant modificationTime = modificationTimeProvider.get(localPath, pathOnContainer);\n          Optional<FilePermissions> permissions =\n              determinePermissions(\n                  pathOnContainer, extraDirectoryPermissions, permissionsPathMatchers);\n          if (permissions.isPresent()) {\n            builder.addEntry(localPath, pathOnContainer, permissions.get(), modificationTime);\n          } else {\n            builder.addEntry(localPath, pathOnContainer, modificationTime);\n          }\n        });\n    return builder.build();\n  }\n\n  private static Optional<FilePermissions> determinePermissions(\n      AbsoluteUnixPath path,\n      Map<String, FilePermissions> extraDirectoryPermissions,\n      Map<PathMatcher, FilePermissions> permissionsPathMatchers) {\n    // The check is only for optimization. (`permissionsPathMatchers` is constructed from the map.)\n    FilePermissions permissions = extraDirectoryPermissions.get(path.toString());\n    if (permissions != null) {\n      return Optional.of(permissions);\n    }\n\n    // Check for matching globs\n    for (Map.Entry<PathMatcher, FilePermissions> entry : permissionsPathMatchers.entrySet()) {\n      if (entry.getKey().matches(Paths.get(path.toString()))) {\n        return Optional.of(entry.getValue());\n      }\n    }\n    return Optional.empty();\n  }\n\n  /**\n   * Constructs a new {@link JibContainerBuilder} for a WAR project.\n   *\n   * @param javaContainerBuilder Java container builder to start with\n   * @param explodedWar the exploded WAR directory\n   * @param projectArtifactFilename the file names of project artifacts for project dependencies\n   * @return {@link JibContainerBuilder} containing the layers for the exploded WAR\n   * @throws IOException if adding layer contents fails\n   */\n  public static JibContainerBuilder fromExplodedWar(\n      JavaContainerBuilder javaContainerBuilder,\n      Path explodedWar,\n      Set<String> projectArtifactFilename)\n      throws IOException {\n    Path webInfLib = explodedWar.resolve(\"WEB-INF/lib\");\n    Path webInfClasses = explodedWar.resolve(\"WEB-INF/classes\");\n    Predicate<Path> isDependency = path -> path.startsWith(webInfLib);\n    Predicate<Path> isClassFile =\n        // Don't use Path.endsWith(), since Path works on path elements.\n        path -> path.startsWith(webInfClasses) && path.getFileName().toString().endsWith(\".class\");\n    Predicate<Path> isResource = isDependency.or(isClassFile).negate();\n    Predicate<Path> isSnapshot = path -> path.getFileName().toString().contains(\"SNAPSHOT\");\n    Predicate<Path> isProjectDependency =\n        path -> projectArtifactFilename.contains(path.getFileName().toString());\n\n    javaContainerBuilder\n        .setResourcesDestination(RelativeUnixPath.get(\"\"))\n        .setClassesDestination(RelativeUnixPath.get(\"WEB-INF/classes\"))\n        .setDependenciesDestination(RelativeUnixPath.get(\"WEB-INF/lib\"));\n\n    if (Files.exists(explodedWar)) {\n      javaContainerBuilder.addResources(explodedWar, isResource);\n    }\n    if (Files.exists(webInfClasses)) {\n      javaContainerBuilder.addClasses(webInfClasses, isClassFile);\n    }\n    if (Files.exists(webInfLib)) {\n      javaContainerBuilder.addDependencies(\n          new DirectoryWalker(webInfLib)\n              .filterRoot()\n              .filter(isSnapshot.negate())\n              .filter(isProjectDependency.negate())\n              .walk());\n      javaContainerBuilder.addSnapshotDependencies(\n          new DirectoryWalker(webInfLib).filterRoot().filter(isSnapshot).walk());\n      javaContainerBuilder.addProjectDependencies(\n          new DirectoryWalker(webInfLib).filterRoot().filter(isProjectDependency).walk());\n    }\n    return javaContainerBuilder.toContainerBuilder();\n  }\n\n  private JavaContainerBuilderHelper() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InsecureRegistryException;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.http.ResponseException;\nimport com.google.cloud.tools.jib.registry.RegistryCredentialsNotSentException;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Verify;\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Set;\nimport java.util.StringJoiner;\nimport java.util.concurrent.ExecutionException;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport org.apache.http.conn.HttpHostConnectException;\n\n/** Runs Jib and builds helpful error messages. */\n@SuppressWarnings(\"InlineFormatString\")\npublic class JibBuildRunner {\n\n  private static final String STARTUP_MESSAGE_PREFIX_FOR_DOCKER_REGISTRY =\n      \"Containerizing application to \";\n  private static final String SUCCESS_MESSAGE_PREFIX_FOR_DOCKER_REGISTRY =\n      \"Built and pushed image as \";\n\n  private static final String STARTUP_MESSAGE_PREFIX_FOR_DOCKER_DAEMON =\n      \"Containerizing application to Docker daemon as \";\n  private static final String SUCCESS_MESSAGE_PREFIX_FOR_DOCKER_DAEMON =\n      \"Built image to Docker daemon as \";\n\n  private static final String STARTUP_MESSAGE_FORMAT_FOR_TARBALL =\n      \"Containerizing application to file at '%s'...\";\n\n  private static final String SUCCESS_MESSAGE_FORMAT_FOR_TARBALL =\n      \"Built image tarball at \\u001B[36m%s\\u001B[0m\";\n\n  private static CharSequence colorCyan(CharSequence innerText) {\n    return new StringBuilder().append(\"\\u001B[36m\").append(innerText).append(\"\\u001B[0m\");\n  }\n\n  private static String buildMessageWithTargetImageReferences(\n      ImageReference targetImageReference,\n      Set<String> additionalTags,\n      String prefix,\n      String suffix) {\n    StringJoiner successMessageBuilder = new StringJoiner(\", \", prefix, suffix);\n    successMessageBuilder.add(colorCyan(targetImageReference.toString()));\n    for (String tag : additionalTags) {\n      successMessageBuilder.add(colorCyan(targetImageReference.withQualifier(tag).toString()));\n    }\n    return successMessageBuilder.toString();\n  }\n\n  /**\n   * Creates a runner to build an image. Creates a directory for the cache, if needed.\n   *\n   * @param jibContainerBuilder the {@link JibContainerBuilder}\n   * @param containerizer the {@link Containerizer}\n   * @param logger consumer for handling log events\n   * @param helpfulSuggestions suggestions to use in help messages for exceptions\n   * @param targetImageReference the target image reference\n   * @param additionalTags additional tags to push to\n   * @return a {@link JibBuildRunner} for building to a registry\n   */\n  public static JibBuildRunner forBuildImage(\n      JibContainerBuilder jibContainerBuilder,\n      Containerizer containerizer,\n      Consumer<LogEvent> logger,\n      HelpfulSuggestions helpfulSuggestions,\n      ImageReference targetImageReference,\n      Set<String> additionalTags) {\n    return new JibBuildRunner(\n        jibContainerBuilder,\n        containerizer,\n        logger,\n        helpfulSuggestions,\n        buildMessageWithTargetImageReferences(\n            targetImageReference,\n            additionalTags,\n            STARTUP_MESSAGE_PREFIX_FOR_DOCKER_REGISTRY,\n            \"...\"),\n        buildMessageWithTargetImageReferences(\n            targetImageReference, additionalTags, SUCCESS_MESSAGE_PREFIX_FOR_DOCKER_REGISTRY, \"\"));\n  }\n\n  /**\n   * Creates a runner to build to the Docker daemon. Creates a directory for the cache, if needed.\n   *\n   * @param jibContainerBuilder the {@link JibContainerBuilder}\n   * @param containerizer the {@link Containerizer}\n   * @param logger consumer for handling log events\n   * @param helpfulSuggestions suggestions to use in help messages for exceptions\n   * @param targetImageReference the target image reference\n   * @param additionalTags additional tags to push to\n   * @return a {@link JibBuildRunner} for building to a Docker daemon\n   */\n  public static JibBuildRunner forBuildToDockerDaemon(\n      JibContainerBuilder jibContainerBuilder,\n      Containerizer containerizer,\n      Consumer<LogEvent> logger,\n      HelpfulSuggestions helpfulSuggestions,\n      ImageReference targetImageReference,\n      Set<String> additionalTags) {\n    return new JibBuildRunner(\n        jibContainerBuilder,\n        containerizer,\n        logger,\n        helpfulSuggestions,\n        buildMessageWithTargetImageReferences(\n            targetImageReference, additionalTags, STARTUP_MESSAGE_PREFIX_FOR_DOCKER_DAEMON, \"...\"),\n        buildMessageWithTargetImageReferences(\n            targetImageReference, additionalTags, SUCCESS_MESSAGE_PREFIX_FOR_DOCKER_DAEMON, \"\"));\n  }\n\n  /**\n   * Creates a runner to build an image tarball. Creates a directory for the cache, if needed.\n   *\n   * @param jibContainerBuilder the {@link JibContainerBuilder}\n   * @param containerizer the {@link Containerizer}\n   * @param logger consumer for handling log events\n   * @param helpfulSuggestions suggestions to use in help messages for exceptions\n   * @param outputPath the path to output the tarball to\n   * @return a {@link JibBuildRunner} for building a tarball\n   */\n  public static JibBuildRunner forBuildTar(\n      JibContainerBuilder jibContainerBuilder,\n      Containerizer containerizer,\n      Consumer<LogEvent> logger,\n      HelpfulSuggestions helpfulSuggestions,\n      Path outputPath) {\n    return new JibBuildRunner(\n        jibContainerBuilder,\n        containerizer,\n        logger,\n        helpfulSuggestions,\n        String.format(STARTUP_MESSAGE_FORMAT_FOR_TARBALL, outputPath.toString()),\n        String.format(SUCCESS_MESSAGE_FORMAT_FOR_TARBALL, outputPath.toString()));\n  }\n\n  private static void handleRegistryUnauthorizedException(\n      RegistryUnauthorizedException registryUnauthorizedException,\n      HelpfulSuggestions helpfulSuggestions)\n      throws BuildStepsExecutionException {\n    if (registryUnauthorizedException.getHttpResponseException().getStatusCode()\n        == HttpStatusCodes.STATUS_CODE_FORBIDDEN) {\n      // No permissions for registry/repository.\n      throw new BuildStepsExecutionException(\n          helpfulSuggestions.forHttpStatusCodeForbidden(\n              registryUnauthorizedException.getImageReference()),\n          registryUnauthorizedException);\n\n    } else {\n      throw new BuildStepsExecutionException(\n          helpfulSuggestions.forNoCredentialsDefined(\n              registryUnauthorizedException.getImageReference()),\n          registryUnauthorizedException);\n    }\n  }\n\n  private final String startupMessage;\n  private final String successMessage;\n  private final JibContainerBuilder jibContainerBuilder;\n  private final Containerizer containerizer;\n  private final Consumer<LogEvent> logger;\n  private final HelpfulSuggestions helpfulSuggestions;\n  @Nullable private Path imageDigestOutputPath;\n  @Nullable private Path imageIdOutputPath;\n  @Nullable private Path imageJsonOutputPath;\n\n  @VisibleForTesting\n  JibBuildRunner(\n      JibContainerBuilder jibContainerBuilder,\n      Containerizer containerizer,\n      Consumer<LogEvent> logger,\n      HelpfulSuggestions helpfulSuggestions,\n      String startupMessage,\n      String successMessage) {\n    this.jibContainerBuilder = jibContainerBuilder;\n    this.containerizer = containerizer;\n    this.logger = logger;\n    this.helpfulSuggestions = helpfulSuggestions;\n    this.startupMessage = startupMessage;\n    this.successMessage = successMessage;\n  }\n\n  /**\n   * Runs the Jib build.\n   *\n   * @return the built {@link JibContainer}\n   * @throws BuildStepsExecutionException if another exception is thrown during the build\n   * @throws IOException if an I/O exception occurs\n   * @throws CacheDirectoryCreationException if the cache directory could not be created\n   */\n  public JibContainer runBuild()\n      throws BuildStepsExecutionException, IOException, CacheDirectoryCreationException {\n    try {\n      logger.accept(LogEvent.lifecycle(\"\"));\n      logger.accept(LogEvent.lifecycle(startupMessage));\n\n      JibContainer jibContainer = jibContainerBuilder.containerize(containerizer);\n\n      logger.accept(LogEvent.lifecycle(\"\"));\n      logger.accept(LogEvent.lifecycle(successMessage));\n\n      // when an image is built, write out the digest and id\n      if (imageDigestOutputPath != null) {\n        String imageDigest = jibContainer.getDigest().toString();\n        Files.write(imageDigestOutputPath, imageDigest.getBytes(StandardCharsets.UTF_8));\n      }\n      if (imageIdOutputPath != null) {\n        String imageId = jibContainer.getImageId().toString();\n        Files.write(imageIdOutputPath, imageId.getBytes(StandardCharsets.UTF_8));\n      }\n      if (imageJsonOutputPath != null) {\n        ImageMetadataOutput metadataOutput = ImageMetadataOutput.fromJibContainer(jibContainer);\n        String imageJson = metadataOutput.toJson();\n        Files.write(imageJsonOutputPath, imageJson.getBytes(StandardCharsets.UTF_8));\n      }\n\n      return jibContainer;\n\n    } catch (HttpHostConnectException ex) {\n      // Failed to connect to registry.\n      throw new BuildStepsExecutionException(helpfulSuggestions.forHttpHostConnect(), ex);\n\n    } catch (RegistryUnauthorizedException ex) {\n      handleRegistryUnauthorizedException(ex, helpfulSuggestions);\n\n    } catch (RegistryCredentialsNotSentException ex) {\n      throw new BuildStepsExecutionException(helpfulSuggestions.forCredentialsNotSent(), ex);\n\n    } catch (RegistryAuthenticationFailedException ex) {\n      if (ex.getCause() instanceof ResponseException) {\n        handleRegistryUnauthorizedException(\n            new RegistryUnauthorizedException(\n                ex.getServerUrl(), ex.getImageName(), (ResponseException) ex.getCause()),\n            helpfulSuggestions);\n      } else {\n        // Unknown cause\n        throw new BuildStepsExecutionException(helpfulSuggestions.none(), ex);\n      }\n\n    } catch (UnknownHostException ex) {\n      throw new BuildStepsExecutionException(helpfulSuggestions.forUnknownHost(), ex);\n\n    } catch (InsecureRegistryException ex) {\n      throw new BuildStepsExecutionException(helpfulSuggestions.forInsecureRegistry(), ex);\n\n    } catch (RegistryException ex) {\n      String message = Verify.verifyNotNull(ex.getMessage()); // keep null-away happy\n      throw new BuildStepsExecutionException(message, ex);\n\n    } catch (ExecutionException ex) {\n      String message = ex.getCause().getMessage();\n      throw new BuildStepsExecutionException(\n          message == null ? \"(null exception message)\" : message, ex.getCause());\n\n    } catch (InterruptedException ex) {\n      Thread.currentThread().interrupt();\n      throw new BuildStepsExecutionException(helpfulSuggestions.none(), ex);\n    }\n\n    throw new IllegalStateException(\"unreachable\");\n  }\n\n  /**\n   * Set the location where the image digest will be saved. If {@code null} then digest is not\n   * saved.\n   *\n   * @param imageDigestOutputPath the location to write the image digest or {@code null} to skip\n   * @return this\n   */\n  public JibBuildRunner writeImageDigest(@Nullable Path imageDigestOutputPath) {\n    this.imageDigestOutputPath = imageDigestOutputPath;\n    return this;\n  }\n\n  /**\n   * Set the location where the image id will be saved. If {@code null} then digest is not saved.\n   *\n   * @param imageIdOutputPath the location to write the image id or {@code null} to skip\n   * @return this\n   */\n  public JibBuildRunner writeImageId(@Nullable Path imageIdOutputPath) {\n    this.imageIdOutputPath = imageIdOutputPath;\n    return this;\n  }\n\n  /**\n   * Set the location where the image metadata json will be saved. If {@code null} then the metadata\n   * is not saved.\n   *\n   * @param imageJsonOutputPath the location to write the image metadata, or {@code null} to skip\n   * @return this\n   */\n  public JibBuildRunner writeImageJson(@Nullable Path imageJsonOutputPath) {\n    this.imageJsonOutputPath = imageJsonOutputPath;\n    return this;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/MainClassInferenceException.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/** Thrown when main class inference fails. */\npublic class MainClassInferenceException extends Exception {\n\n  MainClassInferenceException(String message) {\n    super(message);\n  }\n\n  MainClassInferenceException(String message, Throwable cause) {\n    super(message, cause);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/MainClassResolver.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.MainClassFinder;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Splitter;\nimport java.io.IOException;\nimport javax.annotation.Nullable;\nimport javax.lang.model.SourceVersion;\n\n/** Infers the main class in an application. */\npublic class MainClassResolver {\n\n  /**\n   * If {@code mainClass} is {@code null}, tries to infer main class in this order:\n   *\n   * <ul>\n   *   <li>1. Looks in a {@code jar} plugin provided by {@code projectProperties} ({@code\n   *       maven-jar-plugin} for maven or {@code jar} task for gradle).\n   *   <li>2. Searches for a class defined with a main method.\n   * </ul>\n   *\n   * <p>Warns if main class provided by {@code projectProperties} is not valid, or throws an error\n   * if no valid main class is found.\n   *\n   * @param configuredMainClass the explicitly configured main class ({@code null} if not\n   *     configured)\n   * @param projectProperties properties containing plugin information and help messages\n   * @return the name of the main class to be used for the container entrypoint\n   * @throws MainClassInferenceException if no valid main class is configured or discovered\n   * @throws IOException if getting the class files from {@code projectProperties} fails\n   */\n  public static String resolveMainClass(\n      @Nullable String configuredMainClass, ProjectProperties projectProperties)\n      throws MainClassInferenceException, IOException {\n    if (configuredMainClass != null) {\n      if (isValidJavaClass(configuredMainClass)) {\n        return configuredMainClass;\n      }\n      throw new MainClassInferenceException(\n          HelpfulSuggestions.forMainClassNotFound(\n              \"'mainClass' configured in \"\n                  + projectProperties.getPluginName()\n                  + \" is not a valid Java class: \"\n                  + configuredMainClass,\n              projectProperties.getPluginName()));\n    }\n\n    projectProperties.log(\n        LogEvent.info(\n            \"Searching for main class... Add a 'mainClass' configuration to '\"\n                + projectProperties.getPluginName()\n                + \"' to improve build speed.\"));\n\n    String mainClassFromJarPlugin = projectProperties.getMainClassFromJarPlugin();\n    if (mainClassFromJarPlugin != null && isValidJavaClass(mainClassFromJarPlugin)) {\n      return mainClassFromJarPlugin;\n    }\n\n    if (mainClassFromJarPlugin != null) {\n      projectProperties.log(\n          LogEvent.warn(\n              \"'mainClass' configured in \"\n                  + projectProperties.getJarPluginName()\n                  + \" is not a valid Java class: \"\n                  + mainClassFromJarPlugin));\n    }\n    projectProperties.log(\n        LogEvent.info(\n            \"Could not find a valid main class from \"\n                + projectProperties.getJarPluginName()\n                + \"; looking into all class files to infer main class.\"));\n\n    MainClassFinder.Result mainClassFinderResult =\n        MainClassFinder.find(projectProperties.getClassFiles(), projectProperties::log);\n    switch (mainClassFinderResult.getType()) {\n      case MAIN_CLASS_FOUND:\n        return mainClassFinderResult.getFoundMainClass();\n\n      case MAIN_CLASS_NOT_FOUND:\n        throw new MainClassInferenceException(\n            HelpfulSuggestions.forMainClassNotFound(\n                \"Main class was not found\", projectProperties.getPluginName()));\n\n      case MULTIPLE_MAIN_CLASSES:\n        throw new MainClassInferenceException(\n            HelpfulSuggestions.forMainClassNotFound(\n                \"Multiple valid main classes were found: \"\n                    + String.join(\", \", mainClassFinderResult.getFoundMainClasses()),\n                projectProperties.getPluginName()));\n\n      default:\n        throw new IllegalStateException(\"Cannot reach here\");\n    }\n  }\n\n  /**\n   * Checks if a string is a valid Java class name.\n   *\n   * @param className the class name to check\n   * @return {@code true} if {@code className} is a valid Java class name; {@code false} otherwise\n   */\n  @VisibleForTesting\n  static boolean isValidJavaClass(String className) {\n    for (String part : Splitter.on('.').split(className)) {\n      if (!SourceVersion.isIdentifier(part)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  private MainClassResolver() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.DockerDaemonImage;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.Ports;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.TarImage;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport com.google.cloud.tools.jib.api.buildplan.LayerObject;\nimport com.google.cloud.tools.jib.api.buildplan.ModificationTimeProvider;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory;\nimport com.google.cloud.tools.jib.global.JibSystemProperties;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Splitter;\nimport com.google.common.base.Verify;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Multimaps;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeFormatterBuilder;\nimport java.time.format.DateTimeParseException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\n\n/**\n * Configures and provides {@code JibBuildRunner} for the image building tasks based on raw plugin\n * configuration values and project properties.\n */\npublic class PluginConfigurationProcessor {\n\n  // Known \"generated\" dependencies -- these require that the underlying system run a build step\n  // before they are available for sync'ing\n  private static final ImmutableList<String> GENERATED_LAYERS =\n      ImmutableList.of(\n          LayerType.PROJECT_DEPENDENCIES.getName(),\n          LayerType.RESOURCES.getName(),\n          LayerType.CLASSES.getName());\n\n  // Known \"constant\" layers -- changes to these layers require a change to the build definition,\n  // which we consider non-syncable. These should not be included in the sync-map.\n  private static final ImmutableList<String> CONST_LAYERS =\n      ImmutableList.of(LayerType.DEPENDENCIES.getName(), LayerType.JVM_ARG_FILES.getName());\n\n  private static final String DEFAULT_JETTY_APP_ROOT = \"/var/lib/jetty/webapps/ROOT\";\n\n  private static final String JIB_CLASSPATH_FILE = \"jib-classpath-file\";\n  private static final String JIB_MAIN_CLASS_FILE = \"jib-main-class-file\";\n  private static final Path DEFAULT_JIB_DIR = Paths.get(\"src\").resolve(\"main\").resolve(\"jib\");\n\n  private PluginConfigurationProcessor() {}\n\n  /**\n   * Generate a runner for image builds to docker daemon.\n   *\n   * @param rawConfiguration the raw configuration from the plugin\n   * @param inferredAuthProvider the plugin specific auth provider\n   * @param projectProperties an plugin specific implementation of {@link ProjectProperties}\n   * @param globalConfig the Jib global config\n   * @param helpfulSuggestions a plugin specific instance of {@link HelpfulSuggestions}\n   * @return new {@link JibBuildRunner} to execute a build\n   * @throws InvalidImageReferenceException if the image reference is invalid\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidAppRootException if the specific path for application root is invalid\n   * @throws IOException if an error occurs creating the container builder\n   * @throws InvalidWorkingDirectoryException if the working directory specified for the build is\n   *     invalid\n   * @throws InvalidPlatformException if there exists a {@link PlatformConfiguration} in the\n   *     specified platforms list that is missing required fields or has invalid values\n   * @throws InvalidContainerVolumeException if a specific container volume is invalid\n   * @throws IncompatibleBaseImageJavaVersionException if the base image java version cannot support\n   *     this build\n   * @throws NumberFormatException if a string to number conversion operation fails\n   * @throws InvalidContainerizingModeException if an invalid {@link ContainerizingMode} was\n   *     specified\n   * @throws InvalidFilesModificationTimeException if configured modification time could not be\n   *     parsed\n   * @throws InvalidCreationTimeException if configured creation time could not be parsed\n   * @throws JibPluginExtensionException if an error occurred while running plugin extensions\n   * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not\n   *     found\n   */\n  public static JibBuildRunner createJibBuildRunnerForDockerDaemonImage(\n      RawConfiguration rawConfiguration,\n      InferredAuthProvider inferredAuthProvider,\n      ProjectProperties projectProperties,\n      GlobalConfig globalConfig,\n      HelpfulSuggestions helpfulSuggestions)\n      throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException,\n          IOException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException, JibPluginExtensionException {\n    ImageReference targetImageReference =\n        getGeneratedTargetDockerTag(rawConfiguration, projectProperties, helpfulSuggestions);\n    DockerDaemonImage targetImage = DockerDaemonImage.named(targetImageReference);\n    Optional<Path> dockerExecutable = rawConfiguration.getDockerExecutable();\n    if (dockerExecutable.isPresent()) {\n      targetImage.setDockerExecutable(dockerExecutable.get());\n    }\n    targetImage.setDockerEnvironment(rawConfiguration.getDockerEnvironment());\n\n    Containerizer containerizer = Containerizer.to(targetImage);\n    Multimaps.asMap(globalConfig.getRegistryMirrors()).forEach(containerizer::addRegistryMirrors);\n\n    JibContainerBuilder jibContainerBuilder =\n        processCommonConfiguration(\n            rawConfiguration, inferredAuthProvider, projectProperties, containerizer);\n    JibContainerBuilder updatedContainerBuilder =\n        projectProperties\n            .runPluginExtensions(rawConfiguration.getPluginExtensions(), jibContainerBuilder)\n            .setFormat(ImageFormat.Docker);\n\n    return JibBuildRunner.forBuildToDockerDaemon(\n            updatedContainerBuilder,\n            containerizer,\n            projectProperties::log,\n            helpfulSuggestions,\n            targetImageReference,\n            rawConfiguration.getToTags())\n        .writeImageDigest(rawConfiguration.getDigestOutputPath())\n        .writeImageId(rawConfiguration.getImageIdOutputPath())\n        .writeImageJson(rawConfiguration.getImageJsonOutputPath());\n  }\n\n  /**\n   * Generate a runner for image builds to tar file.\n   *\n   * @param rawConfiguration the raw configuration from the plugin\n   * @param inferredAuthProvider the plugin specific auth provider\n   * @param projectProperties an plugin specific implementation of {@link ProjectProperties}\n   * @param globalConfig the Jib global config\n   * @param helpfulSuggestions a plugin specific instance of {@link HelpfulSuggestions}\n   * @return new {@link JibBuildRunner} to execute a build\n   * @throws InvalidImageReferenceException if the image reference is invalid\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidAppRootException if the specific path for application root is invalid\n   * @throws IOException if an error occurs creating the container builder\n   * @throws InvalidWorkingDirectoryException if the working directory specified for the build is\n   *     invalid\n   * @throws InvalidPlatformException if there exists a {@link PlatformConfiguration} in the\n   *     specified platforms list that is missing required fields or has invalid values\n   * @throws InvalidContainerVolumeException if a specific container volume is invalid\n   * @throws IncompatibleBaseImageJavaVersionException if the base image java version cannot support\n   *     this build\n   * @throws NumberFormatException if a string to number conversion operation fails\n   * @throws InvalidContainerizingModeException if an invalid {@link ContainerizingMode} was\n   *     specified\n   * @throws InvalidFilesModificationTimeException if configured modification time could not be\n   *     parsed\n   * @throws InvalidCreationTimeException if configured creation time could not be parsed\n   * @throws JibPluginExtensionException if an error occurred while running plugin extensions\n   * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not\n   *     found\n   */\n  public static JibBuildRunner createJibBuildRunnerForTarImage(\n      RawConfiguration rawConfiguration,\n      InferredAuthProvider inferredAuthProvider,\n      ProjectProperties projectProperties,\n      GlobalConfig globalConfig,\n      HelpfulSuggestions helpfulSuggestions)\n      throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException,\n          IOException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          JibPluginExtensionException, ExtraDirectoryNotFoundException {\n    ImageReference targetImageReference =\n        getGeneratedTargetDockerTag(rawConfiguration, projectProperties, helpfulSuggestions);\n    TarImage targetImage =\n        TarImage.at(rawConfiguration.getTarOutputPath()).named(targetImageReference);\n\n    Containerizer containerizer = Containerizer.to(targetImage);\n    Multimaps.asMap(globalConfig.getRegistryMirrors()).forEach(containerizer::addRegistryMirrors);\n\n    JibContainerBuilder jibContainerBuilder =\n        processCommonConfiguration(\n            rawConfiguration, inferredAuthProvider, projectProperties, containerizer);\n    JibContainerBuilder updatedContainerBuilder =\n        projectProperties.runPluginExtensions(\n            rawConfiguration.getPluginExtensions(), jibContainerBuilder);\n\n    return JibBuildRunner.forBuildTar(\n            updatedContainerBuilder,\n            containerizer,\n            projectProperties::log,\n            helpfulSuggestions,\n            rawConfiguration.getTarOutputPath())\n        .writeImageDigest(rawConfiguration.getDigestOutputPath())\n        .writeImageId(rawConfiguration.getImageIdOutputPath())\n        .writeImageJson(rawConfiguration.getImageJsonOutputPath());\n  }\n\n  /**\n   * Generate a runner for image builds to registries.\n   *\n   * @param rawConfiguration the raw configuration from the plugin\n   * @param inferredAuthProvider the plugin specific auth provider\n   * @param projectProperties an plugin specific implementation of {@link ProjectProperties}\n   * @param globalConfig the Jib global config\n   * @param helpfulSuggestions a plugin specific instance of {@link HelpfulSuggestions}\n   * @return new {@link JibBuildRunner} to execute a build\n   * @throws InvalidImageReferenceException if the image reference is invalid\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidAppRootException if the specific path for application root is invalid\n   * @throws IOException if an error occurs creating the container builder\n   * @throws InvalidWorkingDirectoryException if the working directory specified for the build is\n   *     invalid\n   * @throws InvalidPlatformException if there exists a {@link PlatformConfiguration} in the\n   *     specified platforms list that is missing required fields or has invalid values\n   * @throws InvalidContainerVolumeException if a specific container volume is invalid\n   * @throws IncompatibleBaseImageJavaVersionException if the base image java version cannot support\n   *     this build\n   * @throws NumberFormatException if a string to number conversion operation fails\n   * @throws InvalidContainerizingModeException if an invalid {@link ContainerizingMode} was\n   *     specified\n   * @throws InvalidFilesModificationTimeException if configured modification time could not be\n   *     parsed\n   * @throws InvalidCreationTimeException if configured creation time could not be parsed\n   * @throws JibPluginExtensionException if an error occurred while running plugin extensions\n   * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not\n   *     found\n   */\n  public static JibBuildRunner createJibBuildRunnerForRegistryImage(\n      RawConfiguration rawConfiguration,\n      InferredAuthProvider inferredAuthProvider,\n      ProjectProperties projectProperties,\n      GlobalConfig globalConfig,\n      HelpfulSuggestions helpfulSuggestions)\n      throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException,\n          IOException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          JibPluginExtensionException, ExtraDirectoryNotFoundException {\n    Optional<String> image = rawConfiguration.getToImage();\n    Preconditions.checkArgument(image.isPresent());\n\n    ImageReference targetImageReference = ImageReference.parse(image.get());\n    RegistryImage targetImage = RegistryImage.named(targetImageReference);\n\n    configureCredentialRetrievers(\n        rawConfiguration,\n        projectProperties,\n        targetImage,\n        targetImageReference,\n        PropertyNames.TO_AUTH_USERNAME,\n        PropertyNames.TO_AUTH_PASSWORD,\n        rawConfiguration.getToAuth(),\n        inferredAuthProvider,\n        rawConfiguration.getToCredHelper());\n\n    boolean alwaysCacheBaseImage =\n        Boolean.parseBoolean(\n            rawConfiguration.getProperty(PropertyNames.ALWAYS_CACHE_BASE_IMAGE).orElse(\"false\"));\n    Containerizer containerizer =\n        Containerizer.to(targetImage).setAlwaysCacheBaseImage(alwaysCacheBaseImage);\n    Multimaps.asMap(globalConfig.getRegistryMirrors()).forEach(containerizer::addRegistryMirrors);\n\n    JibContainerBuilder jibContainerBuilder =\n        processCommonConfiguration(\n            rawConfiguration, inferredAuthProvider, projectProperties, containerizer);\n    JibContainerBuilder updatedContainerBuilder =\n        projectProperties.runPluginExtensions(\n            rawConfiguration.getPluginExtensions(), jibContainerBuilder);\n\n    return JibBuildRunner.forBuildImage(\n            updatedContainerBuilder,\n            containerizer,\n            projectProperties::log,\n            helpfulSuggestions,\n            targetImageReference,\n            rawConfiguration.getToTags())\n        .writeImageDigest(rawConfiguration.getDigestOutputPath())\n        .writeImageId(rawConfiguration.getImageIdOutputPath())\n        .writeImageJson(rawConfiguration.getImageJsonOutputPath());\n  }\n\n  /**\n   * Generate a skaffold syncmap JSON string for an image build configuration.\n   *\n   * @param rawConfiguration the raw configuration from the plugin\n   * @param projectProperties an plugin specific implementation of {@link ProjectProperties}\n   * @param excludes a set of paths to exclude, directories include in this list will be expanded\n   * @return new json string representation of the Sync Map\n   * @throws InvalidImageReferenceException if the image reference is invalid\n   * @throws MainClassInferenceException if a main class could not be found\n   * @throws InvalidAppRootException if the specific path for application root is invalid\n   * @throws IOException if an error occurs creating the container builder\n   * @throws InvalidWorkingDirectoryException if the working directory specified for the build is\n   *     invalid\n   * @throws InvalidPlatformException if there exists a {@link PlatformConfiguration} in the\n   *     specified platforms list that is missing required fields or has invalid values\n   * @throws InvalidContainerVolumeException if a specific container volume is invalid\n   * @throws IncompatibleBaseImageJavaVersionException if the base image java version cannot support\n   *     this build\n   * @throws NumberFormatException if a string to number conversion operation fails\n   * @throws InvalidContainerizingModeException if an invalid {@link ContainerizingMode} was\n   *     specified\n   * @throws InvalidFilesModificationTimeException if configured modification time could not be\n   *     parsed\n   * @throws InvalidCreationTimeException if configured creation time could not be parsed\n   * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not\n   *     found\n   */\n  public static String getSkaffoldSyncMap(\n      RawConfiguration rawConfiguration, ProjectProperties projectProperties, Set<Path> excludes)\n      throws IOException, InvalidCreationTimeException, InvalidImageReferenceException,\n          IncompatibleBaseImageJavaVersionException, InvalidPlatformException,\n          InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException,\n          InvalidWorkingDirectoryException, InvalidFilesModificationTimeException,\n          InvalidContainerizingModeException, ExtraDirectoryNotFoundException {\n    JibContainerBuilder jibContainerBuilder =\n        processCommonConfiguration(\n            rawConfiguration, ignored -> Optional.empty(), projectProperties);\n    SkaffoldSyncMapTemplate syncMap = new SkaffoldSyncMapTemplate();\n    // since jib has already expanded out directories after processing everything, we just\n    // ignore directories and provide only files to watch\n    Set<Path> excludesExpanded = getAllFiles(excludes);\n    for (LayerObject layerObject : jibContainerBuilder.toContainerBuildPlan().getLayers()) {\n      Verify.verify(\n          layerObject instanceof FileEntriesLayer,\n          \"layer types other than FileEntriesLayer not yet supported in build plan layers\");\n      FileEntriesLayer layer = (FileEntriesLayer) layerObject;\n      if (CONST_LAYERS.contains(layer.getName())) {\n        continue;\n      }\n      if (GENERATED_LAYERS.contains(layer.getName())) {\n        layer.getEntries().stream()\n            .filter(layerEntry -> Files.isRegularFile(layerEntry.getSourceFile()))\n            .filter(\n                layerEntry ->\n                    !excludesExpanded.contains(layerEntry.getSourceFile().toAbsolutePath()))\n            .forEach(syncMap::addGenerated);\n      } else { // this is a direct layer\n        layer.getEntries().stream()\n            .filter(layerEntry -> Files.isRegularFile(layerEntry.getSourceFile()))\n            .filter(\n                layerEntry ->\n                    !excludesExpanded.contains(layerEntry.getSourceFile().toAbsolutePath()))\n            .forEach(syncMap::addDirect);\n      }\n    }\n    return syncMap.getJsonString();\n  }\n\n  /** Expand directories to files (excludes directory paths). */\n  static Set<Path> getAllFiles(Set<Path> paths) throws IOException {\n    Set<Path> expanded = new HashSet<>();\n    for (Path path : paths) {\n      if (Files.isRegularFile(path)) {\n        expanded.add(path);\n      } else if (Files.isDirectory(path)) {\n        try (Stream<Path> dirWalk = Files.walk(path)) {\n          dirWalk.filter(Files::isRegularFile).forEach(expanded::add);\n        }\n      }\n    }\n    return expanded;\n  }\n\n  @VisibleForTesting\n  static JibContainerBuilder processCommonConfiguration(\n      RawConfiguration rawConfiguration,\n      InferredAuthProvider inferredAuthProvider,\n      ProjectProperties projectProperties)\n      throws InvalidFilesModificationTimeException, InvalidAppRootException,\n          IncompatibleBaseImageJavaVersionException, IOException, InvalidImageReferenceException,\n          InvalidContainerizingModeException, MainClassInferenceException, InvalidPlatformException,\n          InvalidContainerVolumeException, InvalidWorkingDirectoryException,\n          InvalidCreationTimeException, ExtraDirectoryNotFoundException {\n\n    // Create and configure JibContainerBuilder\n    ModificationTimeProvider modificationTimeProvider =\n        createModificationTimeProvider(rawConfiguration.getFilesModificationTime());\n    JavaContainerBuilder javaContainerBuilder =\n        getJavaContainerBuilderWithBaseImage(\n                rawConfiguration, projectProperties, inferredAuthProvider)\n            .setAppRoot(getAppRootChecked(rawConfiguration, projectProperties))\n            .setModificationTimeProvider(modificationTimeProvider);\n    JibContainerBuilder jibContainerBuilder =\n        projectProperties.createJibContainerBuilder(\n            javaContainerBuilder,\n            getContainerizingModeChecked(rawConfiguration, projectProperties));\n    jibContainerBuilder\n        .setFormat(rawConfiguration.getImageFormat())\n        .setPlatforms(getPlatformsSet(rawConfiguration))\n        .setEntrypoint(computeEntrypoint(rawConfiguration, projectProperties, jibContainerBuilder))\n        .setProgramArguments(rawConfiguration.getProgramArguments().orElse(null))\n        .setEnvironment(rawConfiguration.getEnvironment())\n        .setExposedPorts(Ports.parse(rawConfiguration.getPorts()))\n        .setVolumes(getVolumesSet(rawConfiguration))\n        .setLabels(rawConfiguration.getLabels())\n        .setUser(rawConfiguration.getUser().orElse(null))\n        .setCreationTime(getCreationTime(rawConfiguration.getCreationTime(), projectProperties));\n    getWorkingDirectoryChecked(rawConfiguration)\n        .ifPresent(jibContainerBuilder::setWorkingDirectory);\n\n    // Adds all the extra files.\n    for (ExtraDirectoriesConfiguration extraDirectory : rawConfiguration.getExtraDirectories()) {\n      Path from = extraDirectory.getFrom();\n      if (Files.exists(from)) {\n        jibContainerBuilder.addFileEntriesLayer(\n            JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n                from,\n                AbsoluteUnixPath.get(extraDirectory.getInto()),\n                extraDirectory.getIncludesList(),\n                extraDirectory.getExcludesList(),\n                rawConfiguration.getExtraDirectoryPermissions(),\n                modificationTimeProvider));\n      } else if (!from.endsWith(DEFAULT_JIB_DIR)) {\n        throw new ExtraDirectoryNotFoundException(from.toString(), from.toString());\n      }\n    }\n    return jibContainerBuilder;\n  }\n\n  @VisibleForTesting\n  static JibContainerBuilder processCommonConfiguration(\n      RawConfiguration rawConfiguration,\n      InferredAuthProvider inferredAuthProvider,\n      ProjectProperties projectProperties,\n      Containerizer containerizer)\n      throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException,\n          IOException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    JibSystemProperties.checkHttpTimeoutProperty();\n    JibSystemProperties.checkProxyPortProperty();\n\n    if (JibSystemProperties.sendCredentialsOverHttp()) {\n      projectProperties.log(\n          LogEvent.warn(\n              \"Authentication over HTTP is enabled. It is strongly recommended that you do not \"\n                  + \"enable this on a public network!\"));\n    }\n\n    configureContainerizer(containerizer, rawConfiguration, projectProperties);\n\n    return processCommonConfiguration(rawConfiguration, inferredAuthProvider, projectProperties);\n  }\n\n  /**\n   * Returns a {@link JavaContainerBuilder} with the correctly parsed base image configuration.\n   *\n   * @param rawConfiguration contains the base image configuration\n   * @param projectProperties used for providing additional information\n   * @param inferredAuthProvider provides inferred auths for registry images\n   * @return a new {@link JavaContainerBuilder} with the configured base image\n   * @throws IncompatibleBaseImageJavaVersionException when the Java version in the base image is\n   *     incompatible with the Java version of the application to be containerized\n   * @throws InvalidImageReferenceException if the base image configuration can't be parsed\n   * @throws FileNotFoundException if a credential helper can't be found\n   */\n  @VisibleForTesting\n  static JavaContainerBuilder getJavaContainerBuilderWithBaseImage(\n      RawConfiguration rawConfiguration,\n      ProjectProperties projectProperties,\n      InferredAuthProvider inferredAuthProvider)\n      throws IncompatibleBaseImageJavaVersionException, InvalidImageReferenceException,\n          FileNotFoundException {\n    // Use image configuration as-is if it's a local base image\n    Optional<String> image = rawConfiguration.getFromImage();\n    String baseImageConfig =\n        image.isPresent() ? image.get() : getDefaultBaseImage(projectProperties);\n    if (baseImageConfig.startsWith(Jib.TAR_IMAGE_PREFIX)) {\n      return JavaContainerBuilder.from(baseImageConfig);\n    }\n\n    // Verify Java version is compatible\n    List<String> splits = Splitter.on(\"://\").splitToList(baseImageConfig);\n    String prefixRemoved = splits.get(splits.size() - 1);\n    int javaVersion = projectProperties.getMajorJavaVersion();\n    if (isKnownJava8Image(prefixRemoved) && javaVersion > 8) {\n      throw new IncompatibleBaseImageJavaVersionException(8, javaVersion);\n    }\n    if (isKnownJava11Image(prefixRemoved) && javaVersion > 11) {\n      throw new IncompatibleBaseImageJavaVersionException(11, javaVersion);\n    }\n    if (isKnownJava17Image(prefixRemoved) && javaVersion > 17) {\n      throw new IncompatibleBaseImageJavaVersionException(17, javaVersion);\n    }\n    if (isKnownJava21Image(prefixRemoved) && javaVersion > 21) {\n      throw new IncompatibleBaseImageJavaVersionException(21, javaVersion);\n    }\n    if (isKnownJava25Image(prefixRemoved) && javaVersion > 25) {\n      throw new IncompatibleBaseImageJavaVersionException(25, javaVersion);\n    }\n\n    ImageReference baseImageReference = ImageReference.parse(prefixRemoved);\n    if (baseImageConfig.startsWith(Jib.DOCKER_DAEMON_IMAGE_PREFIX)) {\n      DockerDaemonImage dockerDaemonImage =\n          DockerDaemonImage.named(baseImageReference)\n              .setDockerEnvironment(rawConfiguration.getDockerEnvironment());\n      Optional<Path> dockerExecutable = rawConfiguration.getDockerExecutable();\n      if (dockerExecutable.isPresent()) {\n        dockerDaemonImage.setDockerExecutable(dockerExecutable.get());\n      }\n      return JavaContainerBuilder.from(dockerDaemonImage);\n    }\n\n    RegistryImage baseImage = RegistryImage.named(baseImageReference);\n    configureCredentialRetrievers(\n        rawConfiguration,\n        projectProperties,\n        baseImage,\n        baseImageReference,\n        PropertyNames.FROM_AUTH_USERNAME,\n        PropertyNames.FROM_AUTH_PASSWORD,\n        rawConfiguration.getFromAuth(),\n        inferredAuthProvider,\n        rawConfiguration.getFromCredHelper());\n    return JavaContainerBuilder.from(baseImage);\n  }\n\n  /**\n   * Computes the container entrypoint.\n   *\n   * <p>Computation occurs in this order:\n   *\n   * <ol>\n   *   <li>null (inheriting from the base image), if the user specified value is {@code INHERIT}\n   *   <li>the user specified one, if set\n   *   <li>for a WAR project, null (inheriting) if a custom base image is specified, and {@code\n   *       [\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\"]} otherwise\n   *       (default Jetty base image)\n   *   <li>for a non-WAR project, by resolving the main class\n   * </ol>\n   *\n   * @param rawConfiguration raw configuration data\n   * @param projectProperties used for providing additional information\n   * @param jibContainerBuilder container builder\n   * @return the entrypoint\n   * @throws MainClassInferenceException if no valid main class is configured or discovered\n   * @throws InvalidAppRootException if {@code appRoot} value is not an absolute Unix path\n   * @throws InvalidContainerizingModeException if {@code containerizingMode} value is invalid\n   */\n  @Nullable\n  @VisibleForTesting\n  static List<String> computeEntrypoint(\n      RawConfiguration rawConfiguration,\n      ProjectProperties projectProperties,\n      JibContainerBuilder jibContainerBuilder)\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    Optional<List<String>> rawEntrypoint = rawConfiguration.getEntrypoint();\n    List<String> rawExtraClasspath = rawConfiguration.getExtraClasspath();\n    boolean entrypointDefined = rawEntrypoint.isPresent() && !rawEntrypoint.get().isEmpty();\n\n    if (entrypointDefined\n        && (rawConfiguration.getMainClass().isPresent()\n            || !rawConfiguration.getJvmFlags().isEmpty()\n            || !rawExtraClasspath.isEmpty()\n            || rawConfiguration.getExpandClasspathDependencies())) {\n      projectProperties.log(\n          LogEvent.info(\n              \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored \"\n                  + \"when entrypoint is specified\"));\n    }\n\n    if (projectProperties.isWarProject()) {\n      if (entrypointDefined) {\n        return rawEntrypoint.get().size() == 1 && \"INHERIT\".equals(rawEntrypoint.get().get(0))\n            ? null\n            : rawEntrypoint.get();\n      }\n\n      if (rawConfiguration.getMainClass().isPresent()\n          || !rawConfiguration.getJvmFlags().isEmpty()\n          || !rawExtraClasspath.isEmpty()\n          || rawConfiguration.getExpandClasspathDependencies()) {\n        projectProperties.log(\n            LogEvent.warn(\n                \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored \"\n                    + \"for WAR projects\"));\n      }\n      return rawConfiguration.getFromImage().isPresent()\n          ? null // Inherit if a custom base image.\n          : Arrays.asList(\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\");\n    }\n\n    List<String> classpath = new ArrayList<>(rawExtraClasspath);\n    AbsoluteUnixPath appRoot = getAppRootChecked(rawConfiguration, projectProperties);\n    ContainerizingMode mode = getContainerizingModeChecked(rawConfiguration, projectProperties);\n    switch (mode) {\n      case EXPLODED:\n        classpath.add(appRoot.resolve(\"resources\").toString());\n        classpath.add(appRoot.resolve(\"classes\").toString());\n        break;\n      case PACKAGED:\n        classpath.add(appRoot.resolve(\"classpath/*\").toString());\n        break;\n      default:\n        throw new IllegalStateException(\"unknown containerizing mode: \" + mode);\n    }\n\n    if (projectProperties.getMajorJavaVersion() >= 9\n        || rawConfiguration.getExpandClasspathDependencies()) {\n      List<Path> jars = projectProperties.getDependencies();\n\n      Map<String, Long> occurrences =\n          jars.stream()\n              .map(path -> path.getFileName().toString())\n              .collect(Collectors.groupingBy(filename -> filename, Collectors.counting()));\n      List<String> duplicates =\n          occurrences.entrySet().stream()\n              .filter(entry -> entry.getValue() > 1)\n              .map(Map.Entry::getKey)\n              .collect(Collectors.toList());\n\n      for (Path jar : jars) {\n        // Handle duplicates by appending filesize to the end of the file. This renaming logic\n        // must be in sync with the code that does the same in the other place. See\n        // https://github.com/GoogleContainerTools/jib/issues/3331\n        String jarName = jar.getFileName().toString();\n        if (duplicates.contains(jarName)) {\n          jarName = jarName.replaceFirst(\"\\\\.jar$\", \"-\" + Files.size(jar)) + \".jar\";\n        }\n        classpath.add(appRoot.resolve(\"libs\").resolve(jarName).toString());\n      }\n    } else {\n      classpath.add(appRoot.resolve(\"libs/*\").toString());\n    }\n\n    String classpathString = String.join(\":\", classpath);\n    String mainClass;\n    try {\n      mainClass =\n          MainClassResolver.resolveMainClass(\n              rawConfiguration.getMainClass().orElse(null), projectProperties);\n    } catch (MainClassInferenceException ex) {\n      if (entrypointDefined) {\n        // We will use the user-given entrypoint, so don't fail.\n        mainClass = \"could-not-infer-a-main-class\";\n      } else {\n        throw ex;\n      }\n    }\n    addJvmArgFilesLayer(\n        rawConfiguration, projectProperties, jibContainerBuilder, classpathString, mainClass);\n\n    if (projectProperties.getMajorJavaVersion() >= 9) {\n      classpathString = \"@\" + appRoot.resolve(JIB_CLASSPATH_FILE);\n    }\n\n    if (entrypointDefined) {\n      return rawEntrypoint.get().size() == 1 && \"INHERIT\".equals(rawEntrypoint.get().get(0))\n          ? null\n          : rawEntrypoint.get();\n    }\n\n    List<String> entrypoint = new ArrayList<>(4 + rawConfiguration.getJvmFlags().size());\n    entrypoint.add(\"java\");\n    entrypoint.addAll(rawConfiguration.getJvmFlags());\n    entrypoint.add(\"-cp\");\n    entrypoint.add(classpathString);\n    entrypoint.add(mainClass);\n    return entrypoint;\n  }\n\n  @VisibleForTesting\n  static void addJvmArgFilesLayer(\n      RawConfiguration rawConfiguration,\n      ProjectProperties projectProperties,\n      JibContainerBuilder jibContainerBuilder,\n      String classpath,\n      String mainClass)\n      throws IOException, InvalidAppRootException {\n    Path projectCache = projectProperties.getDefaultCacheDirectory();\n    Path classpathFile = projectCache.resolve(JIB_CLASSPATH_FILE);\n    Path mainClassFile = projectCache.resolve(JIB_MAIN_CLASS_FILE);\n\n    // It's perfectly fine to always generate a new temp file or rewrite an existing file. However,\n    // fixing the source file path and preserving the file timestamp prevents polluting the Jib\n    // layer cache space by not creating new cache selectors every time. (Note, however, creating\n    // new selectors does not affect correctness at all.)\n    writeFileConservatively(classpathFile, classpath);\n    writeFileConservatively(mainClassFile, mainClass);\n\n    AbsoluteUnixPath appRoot = getAppRootChecked(rawConfiguration, projectProperties);\n    jibContainerBuilder.addFileEntriesLayer(\n        FileEntriesLayer.builder()\n            .setName(LayerType.JVM_ARG_FILES.getName())\n            .addEntry(classpathFile, appRoot.resolve(JIB_CLASSPATH_FILE))\n            .addEntry(mainClassFile, appRoot.resolve(JIB_MAIN_CLASS_FILE))\n            .build());\n  }\n\n  /**\n   * Writes a file only when needed (when the file does not exist or the existing file has a\n   * different content). It reads the entire bytes into a {@code String} for content comparison, so\n   * care should be taken when using this method for a huge file.\n   *\n   * @param file target file to write\n   * @param content file content to write\n   * @throws IOException if file I/O error\n   */\n  @VisibleForTesting\n  static void writeFileConservatively(Path file, String content) throws IOException {\n    if (Files.exists(file)) {\n      String oldContent = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);\n      if (oldContent.equals(content)) {\n        return;\n      }\n    }\n    Files.createDirectories(file.getParent());\n    Files.write(file, content.getBytes(StandardCharsets.UTF_8));\n  }\n\n  /**\n   * Gets the suitable value for the base image. If the raw base image parameter is null, returns\n   * {@code \"jetty\"} for WAR projects, or {@code \"eclipse-temurin:{8|11|17}-jre\"} for non-WAR.\n   *\n   * @param projectProperties used for providing additional information\n   * @return the base image\n   * @throws IncompatibleBaseImageJavaVersionException when the Java version in the base image is\n   *     incompatible with the Java version of the application to be containerized\n   */\n  @VisibleForTesting\n  static String getDefaultBaseImage(ProjectProperties projectProperties)\n      throws IncompatibleBaseImageJavaVersionException {\n    if (projectProperties.isWarProject()) {\n      return \"jetty\";\n    }\n    int javaVersion = projectProperties.getMajorJavaVersion();\n    if (javaVersion <= 8) {\n      return \"eclipse-temurin:8-jre\";\n    } else if (javaVersion <= 11) {\n      return \"eclipse-temurin:11-jre\";\n    } else if (javaVersion <= 17) {\n      return \"eclipse-temurin:17-jre\";\n    } else if (javaVersion <= 21) {\n      return \"eclipse-temurin:21-jre\";\n    } else if (javaVersion <= 25) {\n      return \"eclipse-temurin:25-jre\";\n    }\n    throw new IncompatibleBaseImageJavaVersionException(25, javaVersion);\n  }\n\n  /**\n   * Parses the list of platforms to a set of {@link Platform}.\n   *\n   * @param rawConfiguration raw configuration data\n   * @return the set of parsed platforms\n   * @throws InvalidPlatformException if there exists a {@link PlatformConfiguration} in the\n   *     specified platforms list that is missing required fields or has invalid values\n   */\n  @VisibleForTesting\n  static Set<Platform> getPlatformsSet(RawConfiguration rawConfiguration)\n      throws InvalidPlatformException {\n    Set<Platform> platforms = new LinkedHashSet<>();\n    for (PlatformConfiguration platformConfiguration : rawConfiguration.getPlatforms()) {\n      Optional<String> architecture = platformConfiguration.getArchitectureName();\n      Optional<String> os = platformConfiguration.getOsName();\n      String platformToString =\n          \"architecture=\" + architecture.orElse(\"<missing>\") + \", os=\" + os.orElse(\"<missing>\");\n\n      if (!architecture.isPresent()) {\n        throw new InvalidPlatformException(\n            \"platform configuration is missing an architecture value\", platformToString);\n      }\n      if (!os.isPresent()) {\n        throw new InvalidPlatformException(\n            \"platform configuration is missing an OS value\", platformToString);\n      }\n\n      platforms.add(new Platform(architecture.get(), os.get()));\n    }\n    return platforms;\n  }\n\n  /**\n   * Parses the list of raw volumes directories to a set of {@link AbsoluteUnixPath}.\n   *\n   * @param rawConfiguration raw configuration data\n   * @return the set of parsed volumes.\n   * @throws InvalidContainerVolumeException if {@code volumes} are not valid absolute Unix paths\n   */\n  @VisibleForTesting\n  static Set<AbsoluteUnixPath> getVolumesSet(RawConfiguration rawConfiguration)\n      throws InvalidContainerVolumeException {\n    Set<AbsoluteUnixPath> volumes = new HashSet<>();\n    for (String path : rawConfiguration.getVolumes()) {\n      try {\n        AbsoluteUnixPath absoluteUnixPath = AbsoluteUnixPath.get(path);\n        volumes.add(absoluteUnixPath);\n      } catch (IllegalArgumentException exception) {\n        throw new InvalidContainerVolumeException(path, path, exception);\n      }\n    }\n\n    return volumes;\n  }\n\n  /**\n   * Gets the value of the {@code appRoot} parameter. If the parameter is empty, returns {@code\n   * /var/lib/jetty/webapps/ROOT} for WAR projects or {@link JavaContainerBuilder#DEFAULT_APP_ROOT}\n   * for other projects.\n   *\n   * @param rawConfiguration raw configuration data\n   * @param projectProperties the project properties\n   * @return the app root value\n   * @throws InvalidAppRootException if {@code appRoot} value is not an absolute Unix path\n   */\n  @VisibleForTesting\n  static AbsoluteUnixPath getAppRootChecked(\n      RawConfiguration rawConfiguration, ProjectProperties projectProperties)\n      throws InvalidAppRootException {\n    String appRoot = rawConfiguration.getAppRoot();\n    if (appRoot.isEmpty()) {\n      appRoot =\n          projectProperties.isWarProject()\n              ? DEFAULT_JETTY_APP_ROOT\n              : JavaContainerBuilder.DEFAULT_APP_ROOT;\n    }\n    try {\n      return AbsoluteUnixPath.get(appRoot);\n    } catch (IllegalArgumentException ex) {\n      throw new InvalidAppRootException(appRoot, appRoot, ex);\n    }\n  }\n\n  static ContainerizingMode getContainerizingModeChecked(\n      RawConfiguration rawConfiguration, ProjectProperties projectProperties)\n      throws InvalidContainerizingModeException {\n    ContainerizingMode mode = ContainerizingMode.from(rawConfiguration.getContainerizingMode());\n    if (mode == ContainerizingMode.PACKAGED && projectProperties.isWarProject()) {\n      throw new UnsupportedOperationException(\n          \"packaged containerizing mode for WAR is not yet supported\");\n    }\n    return mode;\n  }\n\n  @VisibleForTesting\n  static Optional<AbsoluteUnixPath> getWorkingDirectoryChecked(RawConfiguration rawConfiguration)\n      throws InvalidWorkingDirectoryException {\n    Optional<String> directory = rawConfiguration.getWorkingDirectory();\n    if (!directory.isPresent()) {\n      return Optional.empty();\n    }\n\n    String path = directory.get();\n    try {\n      return Optional.of(AbsoluteUnixPath.get(path));\n    } catch (IllegalArgumentException ex) {\n      throw new InvalidWorkingDirectoryException(path, path, ex);\n    }\n  }\n\n  /**\n   * Creates a modification time provider based on the config value. The value can be:\n   *\n   * <ol>\n   *   <li>{@code EPOCH_PLUS_SECOND} to create a provider which trims file modification time to\n   *       EPOCH + 1 second\n   *   <li>date in ISO 8601 format\n   * </ol>\n   *\n   * @param modificationTime modification time config value\n   * @return corresponding modification time provider\n   * @throws InvalidFilesModificationTimeException if the config value is not in ISO 8601 format\n   */\n  @VisibleForTesting\n  static ModificationTimeProvider createModificationTimeProvider(String modificationTime)\n      throws InvalidFilesModificationTimeException {\n    try {\n      switch (modificationTime) {\n        case \"EPOCH_PLUS_SECOND\":\n          Instant epochPlusSecond = Instant.ofEpochSecond(1);\n          return (ignored1, ignored2) -> epochPlusSecond;\n\n        default:\n          Instant timestamp =\n              DateTimeFormatter.ISO_DATE_TIME.parse(modificationTime, Instant::from);\n          return (ignored1, ignored2) -> timestamp;\n      }\n\n    } catch (DateTimeParseException ex) {\n      throw new InvalidFilesModificationTimeException(modificationTime, modificationTime, ex);\n    }\n  }\n\n  /**\n   * Creates an {@link Instant} based on the config value. The value can be:\n   *\n   * <ol>\n   *   <li>{@code EPOCH} to return epoch\n   *   <li>{@code USE_CURRENT_TIMESTAMP} to return the current time\n   *   <li>date in ISO 8601 format\n   * </ol>\n   *\n   * @param configuredCreationTime the config value\n   * @param projectProperties used for logging warnings\n   * @return corresponding {@link Instant}\n   * @throws InvalidCreationTimeException if the config value is invalid\n   */\n  @VisibleForTesting\n  static Instant getCreationTime(String configuredCreationTime, ProjectProperties projectProperties)\n      throws DateTimeParseException, InvalidCreationTimeException {\n    try {\n      switch (configuredCreationTime) {\n        case \"EPOCH\":\n          return Instant.EPOCH;\n\n        case \"USE_CURRENT_TIMESTAMP\":\n          projectProperties.log(\n              LogEvent.debug(\n                  \"Setting image creation time to current time; your image may not be reproducible.\"));\n          return Instant.now();\n\n        default:\n          DateTimeFormatter formatter =\n              new DateTimeFormatterBuilder()\n                  .append(DateTimeFormatter.ISO_DATE_TIME) // parses isoStrict\n                  // add ability to parse with no \":\" in tz\n                  .optionalStart()\n                  .appendOffset(\"+HHmm\", \"+0000\")\n                  .optionalEnd()\n                  .toFormatter();\n          return formatter.parse(configuredCreationTime, Instant::from);\n      }\n    } catch (DateTimeParseException ex) {\n      throw new InvalidCreationTimeException(configuredCreationTime, configuredCreationTime, ex);\n    }\n  }\n\n  // TODO: find a way to reduce the number of arguments.\n  private static void configureCredentialRetrievers(\n      RawConfiguration rawConfiguration,\n      ProjectProperties projectProperties,\n      RegistryImage registryImage,\n      ImageReference imageReference,\n      String usernamePropertyName,\n      String passwordPropertyName,\n      AuthProperty rawAuthConfiguration,\n      InferredAuthProvider inferredAuthProvider,\n      CredHelperConfiguration credHelperConfiguration)\n      throws FileNotFoundException {\n    DefaultCredentialRetrievers defaultCredentialRetrievers =\n        DefaultCredentialRetrievers.init(\n            CredentialRetrieverFactory.forImage(\n                imageReference, projectProperties::log, credHelperConfiguration.getEnvironment()));\n    Optional<Credential> optionalCredential =\n        ConfigurationPropertyValidator.getImageCredential(\n            projectProperties::log,\n            usernamePropertyName,\n            passwordPropertyName,\n            rawAuthConfiguration,\n            rawConfiguration);\n    if (optionalCredential.isPresent()) {\n      defaultCredentialRetrievers.setKnownCredential(\n          optionalCredential.get(), rawAuthConfiguration.getAuthDescriptor());\n    } else {\n      try {\n        Optional<AuthProperty> optionalInferredAuth =\n            inferredAuthProvider.inferAuth(imageReference.getRegistry());\n        if (optionalInferredAuth.isPresent()) {\n          AuthProperty auth = optionalInferredAuth.get();\n          String username = Verify.verifyNotNull(auth.getUsername());\n          String password = Verify.verifyNotNull(auth.getPassword());\n          Credential credential = Credential.from(username, password);\n          defaultCredentialRetrievers.setInferredCredential(credential, auth.getAuthDescriptor());\n        }\n      } catch (InferredAuthException ex) {\n        projectProperties.log(LogEvent.warn(\"InferredAuthException: \" + ex.getMessage()));\n      }\n    }\n\n    defaultCredentialRetrievers.setCredentialHelper(\n        credHelperConfiguration.getHelperName().orElse(null));\n    defaultCredentialRetrievers.asList().forEach(registryImage::addCredentialRetriever);\n  }\n\n  private static ImageReference getGeneratedTargetDockerTag(\n      RawConfiguration rawConfiguration,\n      ProjectProperties projectProperties,\n      HelpfulSuggestions helpfulSuggestions)\n      throws InvalidImageReferenceException {\n    return ConfigurationPropertyValidator.getGeneratedTargetDockerTag(\n        rawConfiguration.getToImage().orElse(null), projectProperties, helpfulSuggestions);\n  }\n\n  /**\n   * Configures a {@link Containerizer} with values pulled from project properties/raw build\n   * configuration.\n   *\n   * @param containerizer the {@link Containerizer} to configure\n   * @param rawConfiguration the raw build configuration\n   * @param projectProperties the project properties\n   */\n  private static void configureContainerizer(\n      Containerizer containerizer,\n      RawConfiguration rawConfiguration,\n      ProjectProperties projectProperties) {\n    projectProperties.configureEventHandlers(containerizer);\n    containerizer\n        .setOfflineMode(projectProperties.isOffline())\n        .setToolName(projectProperties.getToolName())\n        .setToolVersion(projectProperties.getToolVersion())\n        .setAllowInsecureRegistries(rawConfiguration.getAllowInsecureRegistries())\n        .setBaseImageLayersCache(\n            getCheckedCacheDirectory(\n                PropertyNames.BASE_IMAGE_CACHE,\n                Boolean.getBoolean(PropertyNames.USE_ONLY_PROJECT_CACHE)\n                    ? projectProperties.getDefaultCacheDirectory()\n                    : Containerizer.DEFAULT_BASE_CACHE_DIRECTORY))\n        .setApplicationLayersCache(\n            getCheckedCacheDirectory(\n                PropertyNames.APPLICATION_CACHE, projectProperties.getDefaultCacheDirectory()));\n\n    rawConfiguration.getToTags().forEach(containerizer::withAdditionalTag);\n  }\n\n  /**\n   * Returns the value of a cache directory system property if it is set, otherwise returns {@code\n   * defaultPath}.\n   *\n   * @param property the name of the system property to check\n   * @param defaultPath the path to return if the system property isn't set\n   * @return the value of a cache directory system property if it is set, otherwise returns {@code\n   *     defaultPath}\n   */\n  private static Path getCheckedCacheDirectory(String property, Path defaultPath) {\n    if (System.getProperty(property) != null) {\n      return Paths.get(System.getProperty(property));\n    }\n    return defaultPath;\n  }\n\n  /**\n   * Checks if the given image is a known Java 8 image. May return false negative.\n   *\n   * @param imageReference the image reference\n   * @return {@code true} if the image is a known Java 8 image\n   */\n  private static boolean isKnownJava8Image(String imageReference) {\n    return imageReference.startsWith(\"adoptopenjdk:8\")\n        || imageReference.startsWith(\"eclipse-temurin:8\");\n  }\n\n  /**\n   * Checks if the given image is a known Java 11 image. May return false negative.\n   *\n   * @param imageReference the image reference\n   * @return {@code true} if the image is a known Java 11 image\n   */\n  private static boolean isKnownJava11Image(String imageReference) {\n    return imageReference.startsWith(\"adoptopenjdk:11\")\n        || imageReference.startsWith(\"eclipse-temurin:11\");\n  }\n\n  /**\n   * Checks if the given image is a known Java 17 image. May return false negative.\n   *\n   * @param imageReference the image reference\n   * @return {@code true} if the image is a known Java 17 image\n   */\n  private static boolean isKnownJava17Image(String imageReference) {\n    return imageReference.startsWith(\"eclipse-temurin:17\");\n  }\n\n  /**\n   * Checks if the given image is a known Java 21 image. May return false negative.\n   *\n   * @param imageReference the image reference\n   * @return {@code true} if the image is a known Java 21 image\n   */\n  private static boolean isKnownJava21Image(String imageReference) {\n    return imageReference.startsWith(\"eclipse-temurin:21\");\n  }\n\n  /**\n   * Checks if the given image is a known Java 25 image. May return false negative.\n   *\n   * @param imageReference the image reference\n   * @return {@code true} if the image is a known Java 25 image\n   */\n  private static boolean isKnownJava25Image(String imageReference) {\n    return imageReference.startsWith(\"eclipse-temurin:25\");\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginExtensionLogger.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.plugins.extension.ExtensionLogger;\nimport java.util.function.Consumer;\n\n/** Logger provided to plugin extensions. */\npublic class PluginExtensionLogger implements ExtensionLogger {\n\n  private final Consumer<LogEvent> logger;\n\n  public PluginExtensionLogger(Consumer<LogEvent> logger) {\n    this.logger = logger;\n  }\n\n  @Override\n  public void log(ExtensionLogger.LogLevel logLevel, String message) {\n    switch (logLevel) {\n      case ERROR:\n        logger.accept(LogEvent.error(message));\n        break;\n      case WARN:\n        logger.accept(LogEvent.warn(message));\n        break;\n      case LIFECYCLE:\n        logger.accept(LogEvent.lifecycle(message));\n        break;\n      case INFO:\n        logger.accept(LogEvent.info(message));\n        break;\n      case DEBUG:\n        logger.accept(LogEvent.debug(message));\n        break;\n      default:\n        throw new RuntimeException();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ProjectProperties.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration;\nimport com.google.cloud.tools.jib.plugins.extension.JibPluginExtensionException;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** Project property methods that require maven/gradle-specific implementations. */\npublic interface ProjectProperties {\n\n  /** Directory name for the cache. The directory will be relative to the build output directory. */\n  String CACHE_DIRECTORY_NAME = \"jib-cache\";\n\n  // TODO: Move out of ProjectProperties.\n  void waitForLoggingThread();\n\n  /**\n   * Adds the plugin's event handlers to a containerizer.\n   *\n   * @param containerizer the containerizer to add event handlers to\n   */\n  // TODO: Move out of ProjectProperties.\n  void configureEventHandlers(Containerizer containerizer);\n\n  void log(LogEvent logEvent);\n\n  String getToolName();\n\n  String getToolVersion();\n\n  String getPluginName();\n\n  /**\n   * Starts the containerization process.\n   *\n   * @param javaContainerBuilder Java container builder to start with\n   * @param containerizingMode mode to containerize the app\n   * @return a {@link JibContainerBuilder} with classes, resources, and dependencies added to it\n   * @throws IOException if there is a problem walking the project files\n   */\n  JibContainerBuilder createJibContainerBuilder(\n      JavaContainerBuilder javaContainerBuilder, ContainerizingMode containerizingMode)\n      throws IOException;\n\n  List<Path> getClassFiles() throws IOException;\n\n  List<Path> getDependencies();\n\n  Path getDefaultCacheDirectory();\n\n  String getJarPluginName();\n\n  /**\n   * Returns the name of the main class configured in a jar plugin, or null if none is found.\n   *\n   * @return the name of the main class configured in a jar plugin, or {@code null} if none is found\n   */\n  @Nullable\n  String getMainClassFromJarPlugin();\n\n  boolean isWarProject();\n\n  String getName();\n\n  String getVersion();\n\n  int getMajorJavaVersion();\n\n  boolean isOffline();\n\n  JibContainerBuilder runPluginExtensions(\n      List<? extends ExtensionConfiguration> extensionConfigs,\n      JibContainerBuilder jibContainerBuilder)\n      throws JibPluginExtensionException;\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\n/** Names of system properties used to set configuration via commandline. */\npublic class PropertyNames {\n\n  public static final String FROM_IMAGE = \"jib.from.image\";\n  public static final String FROM_CRED_HELPER = \"jib.from.credHelper\";\n  public static final String FROM_AUTH_USERNAME = \"jib.from.auth.username\";\n  public static final String FROM_AUTH_PASSWORD = \"jib.from.auth.password\";\n  public static final String FROM_PLATFORMS = \"jib.from.platforms\";\n  public static final String TO_IMAGE = \"jib.to.image\";\n  public static final String TO_IMAGE_ALTERNATE = \"image\";\n  public static final String TO_TAGS = \"jib.to.tags\";\n  public static final String TO_CRED_HELPER = \"jib.to.credHelper\";\n  public static final String TO_AUTH_USERNAME = \"jib.to.auth.username\";\n  public static final String TO_AUTH_PASSWORD = \"jib.to.auth.password\";\n  public static final String CONTAINER_APP_ROOT = \"jib.container.appRoot\";\n  public static final String CONTAINER_ARGS = \"jib.container.args\";\n  public static final String CONTAINER_EXTRA_CLASSPATH = \"jib.container.extraClasspath\";\n  public static final String EXPAND_CLASSPATH_DEPENDENCIES =\n      \"jib.container.expandClasspathDependencies\";\n  public static final String CONTAINER_ENTRYPOINT = \"jib.container.entrypoint\";\n  public static final String CONTAINER_ENVIRONMENT = \"jib.container.environment\";\n  public static final String CONTAINER_FORMAT = \"jib.container.format\";\n  public static final String CONTAINER_JVM_FLAGS = \"jib.container.jvmFlags\";\n  public static final String CONTAINER_LABELS = \"jib.container.labels\";\n  public static final String CONTAINER_MAIN_CLASS = \"jib.container.mainClass\";\n  public static final String CONTAINER_USER = \"jib.container.user\";\n  public static final String CONTAINER_WORKING_DIRECTORY = \"jib.container.workingDirectory\";\n  public static final String CONTAINER_VOLUMES = \"jib.container.volumes\";\n  public static final String CONTAINER_PORTS = \"jib.container.ports\";\n  public static final String CONTAINER_FILES_MODIFICATION_TIME =\n      \"jib.container.filesModificationTime\";\n  public static final String CONTAINER_CREATION_TIME = \"jib.container.creationTime\";\n  public static final String ALLOW_INSECURE_REGISTRIES = \"jib.allowInsecureRegistries\";\n  public static final String EXTRA_DIRECTORIES_PATHS = \"jib.extraDirectories.paths\";\n  public static final String EXTRA_DIRECTORIES_PERMISSIONS = \"jib.extraDirectories.permissions\";\n  public static final String DOCKER_CLIENT_EXECUTABLE = \"jib.dockerClient.executable\";\n  public static final String DOCKER_CLIENT_ENVIRONMENT = \"jib.dockerClient.environment\";\n  public static final String OUTPUT_PATHS_DIGEST = \"jib.outputPaths.digest\";\n  public static final String OUTPUT_PATHS_IMAGE_ID = \"jib.outputPaths.imageId\";\n  public static final String OUTPUT_PATHS_IMAGE_JSON = \"jib.outputPaths.imageJson\";\n  public static final String OUTPUT_PATHS_TAR = \"jib.outputPaths.tar\";\n  public static final String CONTAINERIZING_MODE = \"jib.containerizingMode\";\n  public static final String CONFIGURATION_NAME = \"jib.configurationName\";\n  public static final String SKIP = \"jib.skip\";\n\n  public static final String CONTAINERIZE = \"jib.containerize\";\n  public static final String CONSOLE = \"jib.console\";\n  public static final String USE_ONLY_PROJECT_CACHE = \"jib.useOnlyProjectCache\";\n  public static final String BASE_IMAGE_CACHE = \"jib.baseImageCache\";\n  public static final String APPLICATION_CACHE = \"jib.applicationCache\";\n  public static final String ALWAYS_CACHE_BASE_IMAGE = \"jib.alwaysCacheBaseImage\";\n  public static final String DISABLE_UPDATE_CHECKS = \"jib.disableUpdateChecks\";\n  public static final String CONFIG_DIRECTORY = \"jib.configDirectory\";\n\n  private PropertyNames() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.ImageFormat;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\n/**\n * Holds raw plugin configuration parameter values. Acts as a common adapter for heterogeneous\n * plugin configuration models.\n */\npublic interface RawConfiguration {\n\n  interface ExtensionConfiguration {\n\n    String getExtensionClass();\n\n    Map<String, String> getProperties();\n\n    Optional<Object> getExtraConfiguration();\n  }\n\n  interface PlatformConfiguration {\n\n    Optional<String> getOsName();\n\n    Optional<String> getArchitectureName();\n  }\n\n  interface ExtraDirectoriesConfiguration {\n\n    Path getFrom();\n\n    String getInto();\n\n    List<String> getIncludesList();\n\n    List<String> getExcludesList();\n  }\n\n  interface CredHelperConfiguration {\n    Optional<String> getHelperName();\n\n    Map<String, String> getEnvironment();\n  }\n\n  Optional<String> getFromImage();\n\n  Optional<String> getToImage();\n\n  AuthProperty getFromAuth();\n\n  AuthProperty getToAuth();\n\n  CredHelperConfiguration getFromCredHelper();\n\n  CredHelperConfiguration getToCredHelper();\n\n  List<? extends PlatformConfiguration> getPlatforms();\n\n  Set<String> getToTags();\n\n  Optional<List<String>> getEntrypoint();\n\n  List<String> getExtraClasspath();\n\n  boolean getExpandClasspathDependencies();\n\n  Optional<List<String>> getProgramArguments();\n\n  Optional<String> getMainClass();\n\n  List<String> getJvmFlags();\n\n  String getAppRoot();\n\n  Map<String, String> getEnvironment();\n\n  Map<String, String> getLabels();\n\n  List<String> getVolumes();\n\n  List<String> getPorts();\n\n  Optional<String> getUser();\n\n  Optional<String> getWorkingDirectory();\n\n  boolean getAllowInsecureRegistries();\n\n  ImageFormat getImageFormat();\n\n  Optional<String> getProperty(String propertyName);\n\n  String getFilesModificationTime();\n\n  String getCreationTime();\n\n  List<? extends ExtraDirectoriesConfiguration> getExtraDirectories();\n\n  Map<String, FilePermissions> getExtraDirectoryPermissions();\n\n  Optional<Path> getDockerExecutable();\n\n  Map<String, String> getDockerEnvironment();\n\n  String getContainerizingMode();\n\n  Path getTarOutputPath();\n\n  Path getDigestOutputPath();\n\n  Path getImageIdOutputPath();\n\n  Path getImageJsonOutputPath();\n\n  List<? extends ExtensionConfiguration> getPluginExtensions();\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/SkaffoldFilesOutput.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Builds a JSON string containing files and directories that <a\n * href=\"https://github.com/GoogleContainerTools/skaffold\">Skaffold</a> should watch for changes\n * (and consequently trigger rebuilds).\n *\n * <p>{@code build} consists of build definitions. Changes in these files/directories indicate that\n * the project structure may have changed, so Skaffold will refresh the file watch list when this\n * happens.\n *\n * <p>{@code inputs} consist of source/resource files/directories. Skaffold will trigger a rebuild\n * when changes are detected in these files.\n *\n * <p>{@code ignore} consists of files/directories that the Skaffold file watcher should not watch.\n *\n * <p>Example:\n *\n * <pre>{@code\n * {\n *   \"build\": [\n *     \"buildFile1\",\n *     \"buildFile2\"\n *   ],\n *   \"inputs\": [\n *     \"src/main/java/\",\n *     \"src/main/resources/\"\n *   ],\n *   \"ignore\": [\n *     \"pathToIgnore\"\n *   ]\n * }\n * }</pre>\n */\npublic class SkaffoldFilesOutput {\n\n  @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)\n  private static class SkaffoldFilesTemplate {\n\n    private final List<String> build = new ArrayList<>();\n\n    private final List<String> inputs = new ArrayList<>();\n\n    private final List<String> ignore = new ArrayList<>();\n  }\n\n  private final SkaffoldFilesTemplate skaffoldFilesTemplate;\n\n  /** Creates an empty {@link SkaffoldFilesOutput}. */\n  public SkaffoldFilesOutput() {\n    skaffoldFilesTemplate = new SkaffoldFilesTemplate();\n  }\n\n  /**\n   * Creates a {@link SkaffoldFilesOutput} from a JSON string.\n   *\n   * @param json the JSON string\n   * @throws IOException if reading the JSON string fails\n   */\n  @VisibleForTesting\n  public SkaffoldFilesOutput(String json) throws IOException {\n    skaffoldFilesTemplate = new ObjectMapper().readValue(json, SkaffoldFilesTemplate.class);\n  }\n\n  /**\n   * Adds a build file/directory.\n   *\n   * @param build the path to the file/directory\n   */\n  public void addBuild(Path build) {\n    skaffoldFilesTemplate.build.add(build.toString());\n  }\n\n  /**\n   * Adds an input file/directory.\n   *\n   * @param inputFile the path to the file/directory\n   */\n  public void addInput(Path inputFile) {\n    skaffoldFilesTemplate.inputs.add(inputFile.toString());\n  }\n\n  /**\n   * Adds an ignored file/directory.\n   *\n   * @param ignoreFile the path to the file/directory\n   */\n  public void addIgnore(Path ignoreFile) {\n    skaffoldFilesTemplate.ignore.add(ignoreFile.toString());\n  }\n\n  @VisibleForTesting\n  public List<String> getBuild() {\n    return skaffoldFilesTemplate.build;\n  }\n\n  @VisibleForTesting\n  public List<String> getInputs() {\n    return skaffoldFilesTemplate.inputs;\n  }\n\n  @VisibleForTesting\n  public List<String> getIgnore() {\n    return skaffoldFilesTemplate.ignore;\n  }\n\n  /**\n   * Gets the added files in JSON format.\n   *\n   * @return the files in a JSON string\n   * @throws IOException if writing out the JSON fails\n   */\n  public String getJsonString() throws IOException {\n    try (OutputStream outputStream = new ByteArrayOutputStream()) {\n      new ObjectMapper().writeValue(outputStream, skaffoldFilesTemplate);\n      return outputStream.toString();\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/SkaffoldInitOutput.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.fasterxml.jackson.annotation.JsonAutoDetect;\nimport com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.annotation.JsonInclude.Include;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport javax.annotation.Nullable;\n\n/**\n * Builds a JSON string containing the configured target image and sub-project name, to be consumed\n * by <a href=\"https://github.com/GoogleContainerTools/skaffold\">Skaffold</a>.\n *\n * <p>Example:\n *\n * <pre>{@code\n * {\n *   \"image\":\"gcr.io/project/test\",\n *   \"project\":\"project-name\"\n * }\n * }</pre>\n */\n@JsonInclude(Include.NON_NULL)\n@JsonAutoDetect(\n    fieldVisibility = Visibility.ANY,\n    setterVisibility = Visibility.NONE,\n    getterVisibility = Visibility.NONE)\npublic class SkaffoldInitOutput {\n\n  @Nullable private String image;\n\n  @Nullable private String project;\n\n  public SkaffoldInitOutput() {}\n\n  /**\n   * Testing visible OutputGenerator, you should NOT use this.\n   *\n   * @param json the json string to convert\n   * @throws IOException if error occurs during json deserialization\n   */\n  @VisibleForTesting\n  public SkaffoldInitOutput(String json) throws IOException {\n    SkaffoldInitOutput skaffoldInitOutput =\n        new ObjectMapper().readValue(json, SkaffoldInitOutput.class);\n    this.image = skaffoldInitOutput.image;\n    this.project = skaffoldInitOutput.project;\n  }\n\n  public void setImage(@Nullable String image) {\n    this.image = image;\n  }\n\n  public void setProject(@Nullable String project) {\n    this.project = project;\n  }\n\n  /**\n   * Gets the added files in JSON format.\n   *\n   * @return the files in a JSON string\n   * @throws IOException if writing out the JSON fails\n   */\n  public String getJsonString() throws IOException {\n    try (OutputStream outputStream = new ByteArrayOutputStream()) {\n      new ObjectMapper().writeValue(outputStream, this);\n      return outputStream.toString();\n    }\n  }\n\n  @VisibleForTesting\n  @Nullable\n  public String getImage() {\n    return image;\n  }\n\n  @VisibleForTesting\n  @Nullable\n  public String getProject() {\n    return project;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/SkaffoldSyncMapTemplate.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableList;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Builds a JSON string containing files and directories that <a\n * href=\"https://github.com/GoogleContainerTools/skaffold\">Skaffold</a> can use for synchronizing\n * files against a remote container.\n *\n * <p>Example:\n *\n * <pre>{@code\n * {\n *   \"generated\": [\n *      {\n *        src: \"fileX-local\",\n *        dest: \"fileX-remote\"\n *      },\n *      {\n *        src: \"dirX-local\",\n *        dest: \"dirX-remote\"\n *      }\n *   ],\n *   \"direct\": [\n *      {\n *        src: \"fileY-local\",\n *        dest: \"fileY-remote\"\n *      },\n *      {\n *        src: \"dirY-local\",\n *        dest: \"dirY-remote\"\n *      },\n *   ]\n * }\n * }</pre>\n */\npublic class SkaffoldSyncMapTemplate implements JsonTemplate {\n\n  /**\n   * A single entry in the skaffold sync map, may be eventually extended to support permissions and\n   * ownership.\n   */\n  public static class FileTemplate implements JsonTemplate {\n    private final String src;\n    private final String dest;\n\n    @JsonCreator\n    public FileTemplate(\n        @JsonProperty(value = \"src\", required = true) String src,\n        @JsonProperty(value = \"dest\", required = true) String dest) {\n      this.src = src;\n      this.dest = dest;\n    }\n\n    @VisibleForTesting\n    public String getSrc() {\n      return src;\n    }\n\n    @VisibleForTesting\n    public String getDest() {\n      return dest;\n    }\n  }\n\n  private final List<FileTemplate> generated = new ArrayList<>();\n  private final List<FileTemplate> direct = new ArrayList<>();\n\n  @VisibleForTesting\n  public static SkaffoldSyncMapTemplate from(String jsonString) throws IOException {\n    return new ObjectMapper().readValue(jsonString, SkaffoldSyncMapTemplate.class);\n  }\n\n  /**\n   * Add a layer entry as a \"generated\" sync entry. Generated sync entries require rebuilds before\n   * files can be sync'd to a running container.\n   *\n   * @param layerEntry the layer entry to add to the generated configuration\n   */\n  public void addGenerated(FileEntry layerEntry) {\n    generated.add(\n        new FileTemplate(\n            layerEntry.getSourceFile().toAbsolutePath().toString(),\n            layerEntry.getExtractionPath().toString()));\n  }\n\n  /**\n   * Add a layer entry as a \"direct\" sync entry. Direct entries can be sync'd to a running container\n   * without rebuilding any files.\n   *\n   * @param layerEntry the layer entry to add to the direct configuration\n   */\n  public void addDirect(FileEntry layerEntry) {\n    direct.add(\n        new FileTemplate(\n            layerEntry.getSourceFile().toAbsolutePath().toString(),\n            layerEntry.getExtractionPath().toString()));\n  }\n\n  /**\n   * Return JSON representation of the SyncMap.\n   *\n   * @return the json string representation of this SyncMap\n   * @throws IOException if json serialization fails\n   */\n  public String getJsonString() throws IOException {\n    try (OutputStream outputStream = new ByteArrayOutputStream()) {\n      new ObjectMapper().writeValue(outputStream, this);\n      return outputStream.toString();\n    }\n  }\n\n  @VisibleForTesting\n  public List<FileTemplate> getGenerated() {\n    return ImmutableList.copyOf(generated);\n  }\n\n  @VisibleForTesting\n  public List<FileTemplate> getDirect() {\n    return ImmutableList.copyOf(direct);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/TimerEventHandler.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport com.google.cloud.tools.jib.event.events.TimerEvent.State;\nimport java.util.function.Consumer;\n\n/** Handles {@link TimerEvent}s by producing log messages. */\npublic class TimerEventHandler implements Consumer<TimerEvent> {\n\n  /**\n   * Gets a {@link StringBuilder} prepended with tabulation based on the number of parents {@code\n   * timer} has.\n   *\n   * @param timer the {@link TimerEvent.Timer} to base the tabulation on\n   * @return a new {@link StringBuilder}\n   */\n  private static StringBuilder getTabs(TimerEvent.Timer timer) {\n    StringBuilder tabs = new StringBuilder();\n    while (timer.getParent().isPresent()) {\n      tabs.append(\"\\t\");\n      timer = timer.getParent().get();\n    }\n    return tabs;\n  }\n\n  /**\n   * Builds the log message for a {@link TimerEvent}, based on its state and duration.\n   *\n   * @param timerEvent the {@link TimerEvent}\n   * @return the built log message\n   */\n  private static String buildLogMessage(TimerEvent timerEvent) {\n    StringBuilder logMessageBuilder = getTabs(timerEvent.getTimer());\n\n    if (timerEvent.getState() == State.START) {\n      return logMessageBuilder.append(\"TIMING\\t\").append(timerEvent.getDescription()).toString();\n\n    } else {\n      return logMessageBuilder\n          .append(\"TIMED\\t\")\n          .append(timerEvent.getDescription())\n          .append(\" : \")\n          .append(timerEvent.getDuration().toNanos() / 1000 / 1000.0)\n          .append(\" ms\")\n          .toString();\n    }\n  }\n\n  private final Consumer<String> logMessageConsumer;\n\n  /**\n   * Creates a new {@link TimerEventHandler} with a consumer for log messages generated by handling\n   * {@link TimerEvent}s.\n   *\n   * @param logMessageConsumer the log message consumer\n   */\n  public TimerEventHandler(Consumer<String> logMessageConsumer) {\n    this.logMessageConsumer = logMessageConsumer;\n  }\n\n  @Override\n  public void accept(TimerEvent timerEvent) {\n    logMessageConsumer.accept(buildLogMessage(timerEvent));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/UpdateChecker.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.http.FailoverHttpClient;\nimport com.google.cloud.tools.jib.http.Request;\nimport com.google.cloud.tools.jib.http.Response;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.StandardCopyOption;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.format.DateTimeParseException;\nimport java.util.Optional;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.function.Consumer;\n\n/** Checks if Jib is up-to-date. */\npublic class UpdateChecker {\n\n  private static final String LAST_UPDATE_CHECK_FILENAME = \"lastUpdateCheck\";\n\n  /** JSON template for content downloaded during version check. */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  private static class VersionJsonTemplate implements JsonTemplate {\n    private String latest = \"\";\n  }\n\n  /**\n   * Begins checking for an update in a separate thread.\n   *\n   * @param executorService the {@link ExecutorService}\n   * @param versionUrl the location to check for the latest version\n   * @param toolName the tool name\n   * @param toolVersion the tool version\n   * @param log {@link Consumer} used to log messages\n   * @return a new {@link UpdateChecker}\n   */\n  public static Future<Optional<String>> checkForUpdate(\n      ExecutorService executorService,\n      String versionUrl,\n      String toolName,\n      String toolVersion,\n      Consumer<LogEvent> log) {\n    return executorService.submit(\n        () ->\n            performUpdateCheck(\n                GlobalConfig.getConfigDir(), toolVersion, versionUrl, toolName, log));\n  }\n\n  @VisibleForTesting\n  static Optional<String> performUpdateCheck(\n      Path configDir,\n      String currentVersion,\n      String versionUrl,\n      String toolName,\n      Consumer<LogEvent> log) {\n    Path lastUpdateCheck = configDir.resolve(LAST_UPDATE_CHECK_FILENAME);\n\n    try {\n      // Check time of last update check\n      if (Files.exists(lastUpdateCheck)) {\n        try {\n          String fileContents =\n              new String(Files.readAllBytes(lastUpdateCheck), StandardCharsets.UTF_8);\n          Instant modifiedTime = Instant.parse(fileContents);\n          if (modifiedTime.plus(Duration.ofDays(1)).isAfter(Instant.now())) {\n            return Optional.empty();\n          }\n        } catch (DateTimeParseException | IOException ex) {\n          // If reading update time failed, file might be corrupt, so delete it\n          log.accept(LogEvent.debug(\"Failed to read lastUpdateCheck; \" + ex.getMessage()));\n          Files.delete(lastUpdateCheck);\n        }\n      }\n\n      // Check for update\n      FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});\n      try {\n        Response response =\n            httpClient.get(\n                new URL(versionUrl),\n                Request.builder()\n                    .setHttpTimeout(3000)\n                    .setUserAgent(\"jib \" + currentVersion + \" \" + toolName)\n                    .build());\n        VersionJsonTemplate version =\n            JsonTemplateMapper.readJson(response.getBody(), VersionJsonTemplate.class);\n\n        Path lastUpdateCheckTemp =\n            Files.createTempFile(configDir, LAST_UPDATE_CHECK_FILENAME, null);\n        lastUpdateCheckTemp.toFile().deleteOnExit();\n        Files.write(lastUpdateCheckTemp, Instant.now().toString().getBytes(StandardCharsets.UTF_8));\n        Files.move(lastUpdateCheckTemp, lastUpdateCheck, StandardCopyOption.REPLACE_EXISTING);\n\n        if (currentVersion.equals(version.latest)) {\n          return Optional.empty();\n        }\n        return Optional.of(version.latest);\n      } finally {\n        httpClient.shutDown();\n      }\n\n    } catch (IOException ex) {\n      log.accept(LogEvent.debug(\"Update check failed; \" + ex.getMessage()));\n    }\n\n    return Optional.empty();\n  }\n\n  /**\n   * Returns the latest Jib version available if the check succeeded and the current version is\n   * outdated, or returns {@code Optional.empty()} if the check was interrupted or did not determine\n   * that a later version was available.\n   *\n   * @param updateMessageFuture the {@link Future} returned by {@link UpdateChecker#checkForUpdate}\n   * @return the latest version, if found, else {@code Optional.empty()}.\n   */\n  public static Optional<String> finishUpdateCheck(Future<Optional<String>> updateMessageFuture) {\n    if (updateMessageFuture.isDone()) {\n      try {\n        return updateMessageFuture.get();\n      } catch (InterruptedException | ExecutionException ex) {\n        // No need to restore the interrupted status. The intention here is to silently consume any\n        // kind of error\n      }\n    }\n    updateMessageFuture.cancel(true);\n    return Optional.empty();\n  }\n\n  private UpdateChecker() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/VersionChecker.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport java.util.function.BiPredicate;\nimport java.util.function.Function;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * A simple version-range checker, intended to check whether a Jib plugin version falls in some\n * range. A version range can be in one of two forms:\n *\n * <ol>\n *   <li>An <em>interval</em> such as {@code [1.0,3.5)}, where square brackets indicate an closed\n *       boundary (includes the value) and parentheses indicates an open boundary (excludes the\n *       actual value). Either of the left or right bounds can be dropped (but not both!) to to\n *       indicate an half-bound. For example, {@code [,4)} will include any version up to but not\n *       including version 4.0.\n *   <li>a single version such a {@code 1.0} which acts as an interval with an open left bound and\n *       akin to {@code [1.0,)}\n * </ol>\n *\n * <p>To support custom version representations, the actual version object type ({@code <V>}) is\n * pluggable. It must implement {@link Comparable}. The versions in the range must have at most 3\n * components (e.g., {@code major.minor.micro}).\n *\n * <p>This class exists as Gradle has no version-range-type class and Maven's {@code\n * org.apache.maven.artifact.versioning.VersionRange} treats a single version as an exact bound.\n * Note that Gradle's {@code org.gradle.util.GradleVersion} class does not support\n * major-version-only versions.\n */\npublic class VersionChecker<V extends Comparable<? super V>> {\n  /** Regular expression to match a single version. */\n  private static final String VERSION_REGEX = \"\\\\d+(\\\\.\\\\d+(\\\\.\\\\d+)?)?\";\n\n  /** Regular expression to match an interval version range. */\n  private static final String INTERVAL_REGEX =\n      \"[\\\\[(](?<left>\" + VERSION_REGEX + \")?,(?<right>\" + VERSION_REGEX + \")?[])]\";\n\n  private static final Pattern INTERVAL_PATTERN = Pattern.compile(INTERVAL_REGEX);\n\n  // Helper functions to avoid the cognitive burden of {@link Comparable#compareTo()}\n\n  /** Return {@code true} if {@code a} is less than {@code b}. */\n  @VisibleForTesting\n  static <T extends Comparable<? super T>> boolean lt(T a, T b) {\n    return a.compareTo(b) < 0;\n  }\n\n  /** Return {@code true} if {@code a} is less than or equal to {@code b}. */\n  @VisibleForTesting\n  static <T extends Comparable<? super T>> boolean le(T a, T b) {\n    return a.compareTo(b) <= 0;\n  }\n\n  /** Return {@code true} if {@code a} is greater than {@code b}. */\n  @VisibleForTesting\n  static <T extends Comparable<? super T>> boolean gt(T a, T b) {\n    return a.compareTo(b) > 0;\n  }\n\n  /** Return {@code true} if {@code a} is greater than or equal to {@code b}. */\n  @VisibleForTesting\n  static <T extends Comparable<? super T>> boolean ge(T a, T b) {\n    return a.compareTo(b) >= 0;\n  }\n\n  /** Responsible for converting a string representation to a comparable version representation. */\n  private Function<String, V> converter;\n\n  public VersionChecker(Function<String, V> converter) {\n    this.converter = converter;\n  }\n\n  /**\n   * Return {@code true} if {@code actualVersion} is contained within the version range represented\n   * by {@code acceptableVersionRange}.\n   *\n   * @param acceptableVersionRange the encoded version range\n   * @param actualVersion the version to be compared\n   * @return true if the version is acceptable\n   * @throws IllegalArgumentException if the version could not be parsed\n   */\n  public boolean compatibleVersion(String acceptableVersionRange, String actualVersion) {\n    V pluginVersion = parseVersion(actualVersion);\n\n    // Treat a single version \"1.4\" as a left bound, equivalent to \"[1.4,)\"\n    if (acceptableVersionRange.matches(VERSION_REGEX)) {\n      return ge(pluginVersion, parseVersion(acceptableVersionRange));\n    }\n\n    // Otherwise ensure it is a version range with bounds\n    Matcher matcher = INTERVAL_PATTERN.matcher(acceptableVersionRange);\n    Preconditions.checkArgument(matcher.matches(), \"invalid version range\");\n    String leftBound = matcher.group(\"left\");\n    String rightBound = matcher.group(\"right\");\n    Preconditions.checkArgument(\n        leftBound != null || rightBound != null, \"left and right bounds cannot both be empty\");\n    BiPredicate<V, V> leftComparator =\n        acceptableVersionRange.startsWith(\"[\") ? VersionChecker::ge : VersionChecker::gt;\n    BiPredicate<V, V> rightComparator =\n        acceptableVersionRange.endsWith(\"]\") ? VersionChecker::le : VersionChecker::lt;\n\n    if (leftBound != null && !leftComparator.test(pluginVersion, parseVersion(leftBound))) {\n      return false;\n    }\n    if (rightBound != null && !rightComparator.test(pluginVersion, parseVersion(rightBound))) {\n      return false;\n    }\n    return true;\n  }\n\n  /**\n   * Parses and returns a version object.\n   *\n   * @return the parsed version\n   * @throws IllegalArgumentException if an exception occurred\n   */\n  private V parseVersion(String versionString) {\n    // catch other exceptions and turn into an IllegalArgumentException\n    try {\n      return converter.apply(versionString);\n    } catch (IllegalArgumentException ex) {\n      throw ex; // rethrow\n    } catch (Throwable ex) {\n      // Gradle's GradleVersion throws all sorts of unchecked exceptions\n      throw new IllegalArgumentException(\"unable to parse '\" + versionString + \"'\", ex);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ZipUtil.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.io.ByteStreams;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\n\n/** Utility class for Zip archives. */\npublic class ZipUtil {\n\n  private ZipUtil() {}\n\n  /**\n   * Unzips {@code archive} into {@code destination}.\n   *\n   * @param archive zip archive to unzip\n   * @param destination target root for unzipping\n   * @throws IOException when I/O error occurs\n   */\n  public static void unzip(Path archive, Path destination) throws IOException {\n    unzip(archive, destination, false);\n  }\n\n  /**\n   * Unzips {@code archive} into {@code destination}.\n   *\n   * @param archive zip archive to unzip\n   * @param destination target root for unzipping\n   * @param enableReproducibleTimestamps whether or not reproducible timestamps should be used\n   * @throws IOException when I/O error occurs\n   * @throws IllegalStateException when reproducible timestamps are enabled but the target root used\n   *     for unzipping is not empty\n   */\n  public static void unzip(Path archive, Path destination, boolean enableReproducibleTimestamps)\n      throws IOException {\n    if (enableReproducibleTimestamps\n        && Files.isDirectory(destination)\n        && destination.toFile().list().length != 0) {\n      throw new IllegalStateException(\n          \"Cannot enable reproducible timestamps. They can only be enabled when the target root doesn't exist or is an empty directory\");\n    }\n    String canonicalDestination = destination.toFile().getCanonicalPath();\n    List<ZipEntry> entries = new ArrayList<>();\n    try (InputStream fileIn = new BufferedInputStream(Files.newInputStream(archive));\n        ZipInputStream zipIn = new ZipInputStream(fileIn)) {\n      for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) {\n        entries.add(entry);\n        Path entryPath = destination.resolve(entry.getName());\n\n        String canonicalTarget = entryPath.toFile().getCanonicalPath();\n        if (!canonicalTarget.startsWith(canonicalDestination + File.separator)) {\n          String offender = entry.getName() + \" from \" + archive;\n          throw new IOException(\"Blocked unzipping files outside destination: \" + offender);\n        }\n\n        if (entry.isDirectory()) {\n          Files.createDirectories(entryPath);\n        } else {\n          if (entryPath.getParent() != null) {\n            Files.createDirectories(entryPath.getParent());\n          }\n          try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(entryPath))) {\n            ByteStreams.copy(zipIn, out);\n          }\n        }\n      }\n    }\n    preserveModificationTimes(destination, entries, enableReproducibleTimestamps);\n  }\n\n  /**\n   * Preserve modification time of files and directories in a zip file. If a directory is not an\n   * entry in the zip file and reproducible timestamps are enabled then its modification timestamp\n   * is set to a constant value.\n   *\n   * @param destination target root for unzipping\n   * @param entries list of entries in zip file\n   * @param enableReproducibleTimestamps whether or not reproducible timestamps should be used\n   * @throws IOException when I/O error occurs\n   */\n  private static void preserveModificationTimes(\n      Path destination, List<ZipEntry> entries, boolean enableReproducibleTimestamps)\n      throws IOException {\n    if (enableReproducibleTimestamps) {\n      FileTime epochPlusOne = FileTime.fromMillis(1000L);\n      new DirectoryWalker(destination)\n          .filter(Files::isDirectory)\n          .walk(path -> Files.setLastModifiedTime(path, epochPlusOne));\n    }\n    for (ZipEntry entry : entries) {\n      Files.setLastModifiedTime(destination.resolve(entry.getName()), entry.getLastModifiedTime());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/globalconfig/GlobalConfig.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.globalconfig;\n\nimport com.google.cloud.tools.jib.filesystem.XdgDirectories;\nimport com.google.cloud.tools.jib.json.JsonTemplateMapper;\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.ListMultimap;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.nio.file.FileAlreadyExistsException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/** Represents read-only Jib global configuration. */\npublic class GlobalConfig {\n\n  private static final String CONFIG_FILENAME = \"config.json\";\n\n  public static GlobalConfig readConfig() throws IOException, InvalidGlobalConfigException {\n    return readConfig(getConfigDir());\n  }\n\n  @VisibleForTesting\n  static GlobalConfig readConfig(Path configDir) throws IOException, InvalidGlobalConfigException {\n    Path configFile = configDir.resolve(CONFIG_FILENAME);\n\n    try {\n      if (Files.exists(configFile)) {\n        GlobalConfigTemplate configJson =\n            JsonTemplateMapper.readJsonFromFile(configFile, GlobalConfigTemplate.class);\n        return from(configJson);\n      }\n\n      // Generate config file if it doesn't exist\n      Files.createDirectories(configDir);\n      Path tempConfigFile = Files.createTempFile(configDir, CONFIG_FILENAME, null);\n      tempConfigFile.toFile().deleteOnExit();\n\n      GlobalConfigTemplate configJson = new GlobalConfigTemplate();\n      try (OutputStream outputStream = Files.newOutputStream(tempConfigFile)) {\n        JsonTemplateMapper.writeTo(configJson, outputStream);\n        Files.move(tempConfigFile, configFile);\n        return from(configJson);\n\n      } catch (FileAlreadyExistsException ex) {\n        // Perhaps created concurrently. Read again.\n        return readConfig(configDir);\n      }\n\n    } catch (InvalidGlobalConfigException ex) {\n      throw new InvalidGlobalConfigException(\n          ex.getMessage()\n              + \"; see https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it \"\n              + \"to fix or you may need to delete \"\n              + configFile);\n\n    } catch (IOException ex) {\n      throw new IOException(\n          \"Failed to create, open, or parse global Jib config file; see \"\n              + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it \"\n              + \"to fix or you may need to delete \"\n              + configFile,\n          ex);\n    }\n  }\n\n  private static GlobalConfig from(GlobalConfigTemplate configJson)\n      throws InvalidGlobalConfigException {\n    ImmutableListMultimap.Builder<String, String> registryMirrors = ImmutableListMultimap.builder();\n    for (RegistryMirrorsTemplate mirrorConfig : configJson.getRegistryMirrors()) {\n      // validation\n      if (Strings.isNullOrEmpty(mirrorConfig.getRegistry())) {\n        throw new InvalidGlobalConfigException(\"'registryMirrors.registry' property is missing\");\n      }\n      if (mirrorConfig.getMirrors().isEmpty()) {\n        throw new InvalidGlobalConfigException(\"'registryMirrors.mirrors' property is missing\");\n      }\n\n      registryMirrors.putAll(mirrorConfig.getRegistry(), mirrorConfig.getMirrors());\n    }\n\n    return new GlobalConfig(configJson.isDisableUpdateCheck(), registryMirrors.build());\n  }\n\n  /**\n   * Returns the config directory set by {@link PropertyNames#CONFIG_DIRECTORY} if not null,\n   * otherwise returns the default config directory.\n   *\n   * @return the config directory set by {@link PropertyNames#CONFIG_DIRECTORY} if not null,\n   *     otherwise returns the default config directory.\n   */\n  public static Path getConfigDir() {\n    String configDirProperty = System.getProperty(PropertyNames.CONFIG_DIRECTORY);\n    if (!Strings.isNullOrEmpty(configDirProperty)) {\n      return Paths.get(configDirProperty);\n    }\n    return XdgDirectories.getConfigHome();\n  }\n\n  private final boolean disableUpdateCheck;\n  private final ImmutableListMultimap<String, String> registryMirrors;\n\n  private GlobalConfig(\n      boolean disableUpdateCheck, ImmutableListMultimap<String, String> registryMirrors) {\n    this.disableUpdateCheck = disableUpdateCheck;\n    this.registryMirrors = registryMirrors;\n  }\n\n  /**\n   * Returns whether to disable update check.\n   *\n   * @return whether update check is disabled\n   */\n  public boolean isDisableUpdateCheck() {\n    return Boolean.getBoolean(PropertyNames.DISABLE_UPDATE_CHECKS) || disableUpdateCheck;\n  }\n\n  /**\n   * Gets the registry mirror configuration.\n   *\n   * @return registry mirrors\n   */\n  public ListMultimap<String, String> getRegistryMirrors() {\n    return registryMirrors;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/globalconfig/GlobalConfigTemplate.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.globalconfig;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport java.util.Collections;\nimport java.util.List;\n\n/** JSON template for the global configuration file. */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class GlobalConfigTemplate implements JsonTemplate {\n\n  private boolean disableUpdateCheck;\n  private List<RegistryMirrorsTemplate> registryMirrors = Collections.emptyList();\n\n  boolean isDisableUpdateCheck() {\n    return disableUpdateCheck;\n  }\n\n  List<RegistryMirrorsTemplate> getRegistryMirrors() {\n    return registryMirrors;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/globalconfig/InvalidGlobalConfigException.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.globalconfig;\n\n/** Exception when the global Jib configuration file cannot be parsed or has invalid values. */\npublic class InvalidGlobalConfigException extends Exception {\n\n  public InvalidGlobalConfigException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/globalconfig/RegistryMirrorsTemplate.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.globalconfig;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.google.cloud.tools.jib.json.JsonTemplate;\nimport java.util.Collections;\nimport java.util.List;\nimport javax.annotation.Nullable;\n\n/** JSON template for registry mirror config. */\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class RegistryMirrorsTemplate implements JsonTemplate {\n\n  @Nullable private String registry;\n  private List<String> mirrors = Collections.emptyList();\n\n  @Nullable\n  String getRegistry() {\n    return registry;\n  }\n\n  List<String> getMirrors() {\n    return mirrors;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/logging/AnsiLoggerWithFooter.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Logs to a console supporting ANSI escape sequences and keeps an additional footer that always\n * appears below log messages.\n */\nclass AnsiLoggerWithFooter implements ConsoleLogger {\n\n  /**\n   * Maximum width of a footer line. Having width too large can mess up the display when the console\n   * width is too small.\n   */\n  private static final int MAX_FOOTER_WIDTH = 50;\n\n  /** ANSI escape sequence template for moving the cursor up multiple lines. */\n  private static final String CURSOR_UP_SEQUENCE_TEMPLATE = \"\\033[%dA\";\n\n  /** ANSI escape sequence for moving the cursor up. */\n  private static final String CURSOR_UP_SEQUENCE = String.format(CURSOR_UP_SEQUENCE_TEMPLATE, 1);\n\n  /** ANSI escape sequence for erasing to end of display. */\n  private static final String ERASE_DISPLAY_BELOW = \"\\033[0J\";\n\n  /** ANSI escape sequence for setting all further characters to bold. */\n  private static final String BOLD = \"\\033[1m\";\n\n  /** ANSI escape sequence for setting all further characters to not bold. */\n  private static final String UNBOLD = \"\\033[0m\";\n\n  /**\n   * Makes sure each line of text in {@code lines} is at most {@link #MAX_FOOTER_WIDTH} characters\n   * long. If a line of text exceeds {@link #MAX_FOOTER_WIDTH} characters, the line is truncated to\n   * {@link #MAX_FOOTER_WIDTH} characters with the last 3 characters as {@code ...}.\n   *\n   * @param lines the lines of text\n   * @return the truncated lines of text\n   */\n  @VisibleForTesting\n  static List<String> truncateToMaxWidth(List<String> lines) {\n    List<String> truncatedLines = new ArrayList<>();\n    for (String line : lines) {\n      if (line.length() > MAX_FOOTER_WIDTH) {\n        truncatedLines.add(line.substring(0, MAX_FOOTER_WIDTH - 3) + \"...\");\n      } else {\n        truncatedLines.add(line);\n      }\n    }\n    return truncatedLines;\n  }\n\n  private final ImmutableMap<Level, Consumer<String>> messageConsumers;\n  private final Consumer<String> lifecycleConsumer;\n  private final SingleThreadedExecutor singleThreadedExecutor;\n\n  private List<String> footerLines = Collections.emptyList();\n\n  // When a footer is erased, makes the logger go up two lines (and then down one line by calling\n  // \"accept()\" once) before printing the next message. This is useful to correct an issue in Maven:\n  // https://github.com/GoogleContainerTools/jib/issues/1952\n  private boolean enableTwoCursorUpJump;\n\n  /**\n   * Creates a new {@link AnsiLoggerWithFooter}.\n   *\n   * @param messageConsumers map from each {@link Level} to a corresponding message logger\n   * @param singleThreadedExecutor a {@link SingleThreadedExecutor} to ensure that all messages are\n   *     logged in a sequential, deterministic order\n   * @param enableTwoCursorUpJump allows the logger to move the cursor up twice at once. Fixes a\n   *     logging issue in Maven (https://github.com/GoogleContainerTools/jib/issues/1952) but causes\n   *     a problem in Gradle (https://github.com/GoogleContainerTools/jib/issues/1963)\n   */\n  AnsiLoggerWithFooter(\n      ImmutableMap<Level, Consumer<String>> messageConsumers,\n      SingleThreadedExecutor singleThreadedExecutor,\n      boolean enableTwoCursorUpJump) {\n    Preconditions.checkArgument(\n        messageConsumers.containsKey(Level.LIFECYCLE),\n        \"Cannot construct AnsiLoggerFooter without LIFECYCLE message consumer\");\n    this.messageConsumers = messageConsumers;\n    lifecycleConsumer = Preconditions.checkNotNull(messageConsumers.get(Level.LIFECYCLE));\n    this.singleThreadedExecutor = singleThreadedExecutor;\n    this.enableTwoCursorUpJump = enableTwoCursorUpJump;\n  }\n\n  @Override\n  public void log(Level logLevel, String message) {\n    if (!messageConsumers.containsKey(logLevel)) {\n      return;\n    }\n    Consumer<String> messageConsumer = messageConsumers.get(logLevel);\n\n    singleThreadedExecutor.execute(\n        () -> {\n          boolean didErase = eraseFooter();\n          // If a previous footer was erased, the message needs to go up a line.\n          if (didErase) {\n            if (enableTwoCursorUpJump) {\n              messageConsumer.accept(String.format(CURSOR_UP_SEQUENCE_TEMPLATE, 2));\n              messageConsumer.accept(message);\n            } else {\n              messageConsumer.accept(CURSOR_UP_SEQUENCE + message);\n            }\n          } else {\n            messageConsumer.accept(message);\n          }\n\n          printInBold(footerLines);\n        });\n  }\n\n  /**\n   * Sets the footer asynchronously. This will replace the previously-printed footer with the new\n   * {@code footerLines}.\n   *\n   * <p>The footer is printed in <strong>bold</strong>.\n   *\n   * @param newFooterLines the footer, with each line as an element (no newline at end)\n   */\n  @Override\n  public void setFooter(List<String> newFooterLines) {\n    List<String> truncatedNewFooterLines = truncateToMaxWidth(newFooterLines);\n\n    if (truncatedNewFooterLines.equals(footerLines)) {\n      return;\n    }\n\n    singleThreadedExecutor.execute(\n        () -> {\n          boolean didErase = eraseFooter();\n          // If a previous footer was erased, the first new footer line needs to go up a line.\n          if (didErase) {\n            if (enableTwoCursorUpJump) {\n              lifecycleConsumer.accept(String.format(CURSOR_UP_SEQUENCE_TEMPLATE, 2));\n              printInBold(truncatedNewFooterLines);\n            } else {\n              printInBold(truncatedNewFooterLines, CURSOR_UP_SEQUENCE);\n            }\n          } else {\n            printInBold(truncatedNewFooterLines);\n          }\n\n          footerLines = truncatedNewFooterLines;\n        });\n  }\n\n  /**\n   * Erases the footer. Do <em>not</em> call outside of a task submitted to {@link\n   * #singleThreadedExecutor}.\n   *\n   * @return {@code true} if anything was erased; {@code false} otherwise\n   */\n  private boolean eraseFooter() {\n    if (footerLines.isEmpty()) {\n      return false;\n    }\n\n    StringBuilder footerEraserBuilder = new StringBuilder();\n\n    // Moves the cursor up to the start of the footer.\n    footerEraserBuilder.append(String.format(CURSOR_UP_SEQUENCE_TEMPLATE, footerLines.size()));\n    // Erases everything below cursor.\n    footerEraserBuilder.append(ERASE_DISPLAY_BELOW);\n\n    lifecycleConsumer.accept(footerEraserBuilder.toString());\n\n    return true;\n  }\n\n  private void printInBold(List<String> lines) {\n    printInBold(lines, \"\");\n  }\n\n  private void printInBold(List<String> lines, String firstLinePrefix) {\n    for (int i = 0; i < lines.size(); i++) {\n      lifecycleConsumer.accept((i == 0 ? firstLinePrefix : \"\") + BOLD + lines.get(i) + UNBOLD);\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/logging/ConsoleLogger.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport java.util.List;\n\n/** Logs messages to the console. Implementations must be thread-safe. */\npublic interface ConsoleLogger {\n\n  /**\n   * Logs {@code message} to the console at {@link Level#LIFECYCLE}.\n   *\n   * @param logLevel the log level for the {@code message}\n   * @param message the message\n   */\n  void log(LogEvent.Level logLevel, String message);\n\n  /**\n   * Sets the footer.\n   *\n   * @param footerLines the footer, with each line as an element (no newline at end)\n   */\n  void setFooter(List<String> footerLines);\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/logging/ConsoleLoggerBuilder.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\n/** Builds a {@link ConsoleLogger}. */\npublic class ConsoleLoggerBuilder {\n\n  /**\n   * Alias for function that takes a map from {@link Level} to a log message {@link Consumer} and\n   * creates a {@link ConsoleLogger}.\n   */\n  @VisibleForTesting\n  @FunctionalInterface\n  interface ConsoleLoggerFactory\n      extends Function<ImmutableMap<Level, Consumer<String>>, ConsoleLogger> {}\n\n  /**\n   * Starts a {@link ConsoleLoggerBuilder} for rich logging (ANSI support with footer).\n   *\n   * @param singleThreadedExecutor a {@link SingleThreadedExecutor} to ensure that all messages are\n   *     logged in a sequential, deterministic order\n   * @param enableTwoCursorUpJump allows the logger to move the cursor up twice at once. Fixes a\n   *     logging issue in Maven (https://github.com/GoogleContainerTools/jib/issues/1952) but causes\n   *     a problem in Gradle (https://github.com/GoogleContainerTools/jib/issues/1963)\n   * @return a new {@link ConsoleLoggerBuilder}\n   */\n  public static ConsoleLoggerBuilder rich(\n      SingleThreadedExecutor singleThreadedExecutor, boolean enableTwoCursorUpJump) {\n    return new ConsoleLoggerBuilder(\n        messageConsumerMap ->\n            new AnsiLoggerWithFooter(\n                messageConsumerMap, singleThreadedExecutor, enableTwoCursorUpJump));\n  }\n\n  /**\n   * Starts a {@link ConsoleLoggerBuilder} for plain-text logging (no ANSI support).\n   *\n   * @param singleThreadedExecutor a {@link SingleThreadedExecutor} to ensure that all messages are\n   *     logged in a sequential, deterministic order\n   * @return a new {@link ConsoleLoggerBuilder}\n   */\n  public static ConsoleLoggerBuilder plain(SingleThreadedExecutor singleThreadedExecutor) {\n    return new ConsoleLoggerBuilder(\n        messageConsumerMap -> new PlainConsoleLogger(messageConsumerMap, singleThreadedExecutor));\n  }\n\n  private final ImmutableMap.Builder<Level, Consumer<String>> messageConsumers =\n      ImmutableMap.builder();\n  private final ConsoleLoggerFactory consoleLoggerFactory;\n\n  @VisibleForTesting\n  ConsoleLoggerBuilder(ConsoleLoggerFactory consoleLoggerFactory) {\n    this.consoleLoggerFactory = consoleLoggerFactory;\n  }\n\n  /**\n   * Sets the {@link Consumer} to log a {@link Level#LIFECYCLE} message.\n   *\n   * @param messageConsumer the message {@link Consumer}\n   * @return this\n   */\n  public ConsoleLoggerBuilder lifecycle(Consumer<String> messageConsumer) {\n    messageConsumers.put(Level.LIFECYCLE, messageConsumer);\n    return this;\n  }\n\n  /**\n   * Sets the {@link Consumer} to log a {@link Level#PROGRESS} message.\n   *\n   * @param messageConsumer the message {@link Consumer}\n   * @return this\n   */\n  public ConsoleLoggerBuilder progress(Consumer<String> messageConsumer) {\n    messageConsumers.put(Level.PROGRESS, messageConsumer);\n    return this;\n  }\n\n  /**\n   * Sets the {@link Consumer} to log a {@link Level#DEBUG} message.\n   *\n   * @param messageConsumer the message {@link Consumer}\n   * @return this\n   */\n  public ConsoleLoggerBuilder debug(Consumer<String> messageConsumer) {\n    messageConsumers.put(Level.DEBUG, messageConsumer);\n    return this;\n  }\n\n  /**\n   * Sets the {@link Consumer} to log an {@link Level#ERROR} message.\n   *\n   * @param messageConsumer the message {@link Consumer}\n   * @return this\n   */\n  public ConsoleLoggerBuilder error(Consumer<String> messageConsumer) {\n    messageConsumers.put(Level.ERROR, messageConsumer);\n    return this;\n  }\n\n  /**\n   * Sets the {@link Consumer} to log an {@link Level#INFO} message.\n   *\n   * @param messageConsumer the message {@link Consumer}\n   * @return this\n   */\n  public ConsoleLoggerBuilder info(Consumer<String> messageConsumer) {\n    messageConsumers.put(Level.INFO, messageConsumer);\n    return this;\n  }\n\n  /**\n   * Sets the {@link Consumer} to log a {@link Level#WARN} message.\n   *\n   * @param messageConsumer the message {@link Consumer}\n   * @return this\n   */\n  public ConsoleLoggerBuilder warn(Consumer<String> messageConsumer) {\n    messageConsumers.put(Level.WARN, messageConsumer);\n    return this;\n  }\n\n  /**\n   * Builds the {@link ConsoleLogger}.\n   *\n   * @return the {@link ConsoleLogger}\n   */\n  public ConsoleLogger build() {\n    return consoleLoggerFactory.apply(messageConsumers.build());\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/logging/PlainConsoleLogger.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/** Logs messages plainly. */\nclass PlainConsoleLogger implements ConsoleLogger {\n\n  private final ImmutableMap<Level, Consumer<String>> messageConsumers;\n  private final SingleThreadedExecutor singleThreadedExecutor;\n\n  /**\n   * Creates a {@link PlainConsoleLogger}.\n   *\n   * @param messageConsumers map from each {@link Level} to a log message {@link Consumer} of type\n   *     {@code Consumer<String>}\n   * @param singleThreadedExecutor a {@link SingleThreadedExecutor} to ensure that all messages are\n   *     logged in a sequential, deterministic order\n   */\n  PlainConsoleLogger(\n      ImmutableMap<Level, Consumer<String>> messageConsumers,\n      SingleThreadedExecutor singleThreadedExecutor) {\n    this.messageConsumers = messageConsumers;\n    this.singleThreadedExecutor = singleThreadedExecutor;\n  }\n\n  @Override\n  public void log(Level logLevel, String message) {\n    if (!messageConsumers.containsKey(logLevel)) {\n      return;\n    }\n    Consumer<String> messageConsumer = messageConsumers.get(logLevel);\n\n    // remove the color from the message\n    final String plainMessage = message.replaceAll(\"\\u001B\\\\[[0-9;]{1,5}m\", \"\");\n    singleThreadedExecutor.execute(() -> messageConsumer.accept(plainMessage));\n  }\n\n  @Override\n  public void setFooter(List<String> footerLines) {\n    // No op.\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/logging/ProgressDisplayGenerator.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Generates a display of progress and unfinished tasks.\n *\n * <p>Example:\n *\n * <p>Executing tasks...<br>\n * [================= ] 72.5% complete<br>\n * &gt; task 1 running<br>\n * &gt; task 3 running\n */\npublic class ProgressDisplayGenerator {\n\n  /** Line above progress bar. */\n  private static final String HEADER = \"Executing tasks:\";\n\n  /** Maximum number of bars in the progress display. */\n  private static final int PROGRESS_BAR_COUNT = 30;\n\n  /**\n   * Generates a progress display.\n   *\n   * @param progress the overall progress, with {@code 1.0} meaning fully complete\n   * @param unfinishedLeafTasks the unfinished leaf tasks\n   * @return the progress display as a list of lines\n   */\n  public static List<String> generateProgressDisplay(\n      double progress, List<String> unfinishedLeafTasks) {\n    List<String> lines = new ArrayList<>();\n\n    lines.add(HEADER);\n    lines.add(generateProgressBar(progress));\n    for (String task : unfinishedLeafTasks) {\n      lines.add(\"> \" + task);\n    }\n\n    return lines;\n  }\n\n  /**\n   * Generates the progress bar line.\n   *\n   * @param progress the overall progress, with {@code 1.0} meaning fully complete\n   * @return the progress bar line\n   */\n  private static String generateProgressBar(double progress) {\n    StringBuilder progressBar = new StringBuilder();\n    progressBar.append('[');\n\n    int barsToDisplay = (int) Math.round(PROGRESS_BAR_COUNT * progress);\n    for (int barIndex = 0; barIndex < PROGRESS_BAR_COUNT; barIndex++) {\n      progressBar.append(barIndex < barsToDisplay ? '=' : ' ');\n    }\n\n    return progressBar\n        .append(']')\n        .append(String.format(\" %.1f\", progress * 100))\n        .append(\"% complete\")\n        .toString();\n  }\n\n  private ProgressDisplayGenerator() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/logging/SingleThreadedExecutor.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport java.time.Duration;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * Executes methods on a single managed thread. Make sure to call {@link\n * #shutDownAndAwaitTermination} when finished.\n *\n * <p>This implementation is thread-safe.\n */\npublic class SingleThreadedExecutor {\n\n  private static final Logger LOGGER = Logger.getLogger(SingleThreadedExecutor.class.getName());\n  private final ExecutorService executorService = Executors.newSingleThreadExecutor();\n\n  /**\n   * Shuts down the {@link #executorService} and waits for it to terminate.\n   *\n   * @param timeout timeout to wait. The method may call {@link ExecutorService#awaitTermination}\n   *     times with the given timeout, so the overall wait time can go up to 2 times the timeout\n   */\n  public void shutDownAndAwaitTermination(Duration timeout) {\n    executorService.shutdown();\n\n    try {\n      if (!executorService.awaitTermination(timeout.getSeconds(), TimeUnit.SECONDS)) {\n        executorService.shutdownNow();\n        if (!executorService.awaitTermination(timeout.getSeconds(), TimeUnit.SECONDS)) {\n          LOGGER.log(Level.SEVERE, \"Could not shut down SingleThreadedExecutor\");\n        }\n      }\n\n    } catch (InterruptedException ex) {\n      executorService.shutdownNow();\n      Thread.currentThread().interrupt();\n    }\n  }\n\n  /**\n   * Executes {@code runnable} on the managed thread.\n   *\n   * @param runnable the {@link Runnable}\n   */\n  public void execute(Runnable runnable) {\n    executorService.execute(runnable);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.blob.Blobs;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport javax.annotation.Nullable;\nimport org.junit.Assert;\n\n/** Test helpers for making HTTP requests. */\npublic class HttpRequestTester {\n\n  /**\n   * Verifies the response body. Repeatedly tries {@code url} at the interval of .5 seconds for up\n   * to 20 seconds until getting OK HTTP response code.\n   */\n  public static void verifyBody(String expectedBody, URL url) throws InterruptedException {\n    Assert.assertEquals(expectedBody, getContent(url));\n  }\n\n  /** Fetches the host to use for the http request. */\n  public static String fetchDockerHostForHttpRequest() {\n    if (System.getenv(\"KOKORO_JOB_CLUSTER\") != null\n        && System.getenv(\"KOKORO_JOB_CLUSTER\").equals(\"MACOS_EXTERNAL\")) {\n      return System.getenv(\"DOCKER_IP\");\n    } else if (System.getenv(\"KOKORO_JOB_CLUSTER\") != null\n        && System.getenv(\"KOKORO_JOB_CLUSTER\").equals(\"GCP_UBUNTU_DOCKER\")) {\n      return System.getenv(\"DOCKER_IP_UBUNTU\");\n    } else {\n      return \"localhost\";\n    }\n  }\n\n  @Nullable\n  private static String getContent(URL url) throws InterruptedException {\n    for (int i = 0; i < 40; i++) {\n      Thread.sleep(500);\n      try {\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {\n          try (InputStream in = connection.getInputStream()) {\n            return Blobs.writeToString(Blobs.from(in));\n          }\n        }\n      } catch (IOException ignored) {\n        // ignored\n      }\n    }\n    return null;\n  }\n\n  private HttpRequestTester() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/api/JibContainerBuilderTestHelper.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.api;\n\nimport com.google.cloud.tools.jib.configuration.BuildContext;\n\n/** Test helper to expose package-private members of {@link JibContainerBuilder}. */\npublic class JibContainerBuilderTestHelper {\n\n  public static BuildContext toBuildContext(\n      JibContainerBuilder jibContainerBuilder, Containerizer containerizer)\n      throws CacheDirectoryCreationException {\n    return jibContainerBuilder.toBuildContext(containerizer);\n  }\n\n  private JibContainerBuilderTestHelper() {}\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidatorTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ConfigurationPropertyValidator}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ConfigurationPropertyValidatorTest {\n\n  @Mock private Consumer<LogEvent> mockLogger;\n  @Mock private AuthProperty mockAuth;\n  @Mock private RawConfiguration mockConfiguration;\n\n  @Test\n  public void testGetImageAuth() {\n    when(mockAuth.getUsernameDescriptor()).thenReturn(\"user\");\n    when(mockAuth.getPasswordDescriptor()).thenReturn(\"pass\");\n    when(mockAuth.getUsername()).thenReturn(\"vwxyz\");\n    when(mockAuth.getPassword()).thenReturn(\"98765\");\n\n    // System properties set\n    when(mockConfiguration.getProperty(\"jib.test.auth.user\")).thenReturn(Optional.of(\"abcde\"));\n    when(mockConfiguration.getProperty(\"jib.test.auth.pass\")).thenReturn(Optional.of(\"12345\"));\n    Credential expected = Credential.from(\"abcde\", \"12345\");\n    Optional<Credential> actual =\n        ConfigurationPropertyValidator.getImageCredential(\n            mockLogger, \"jib.test.auth.user\", \"jib.test.auth.pass\", mockAuth, mockConfiguration);\n    assertThat(actual).hasValue(expected);\n\n    // Auth set in configuration\n    when(mockConfiguration.getProperty(\"jib.test.auth.user\")).thenReturn(Optional.empty());\n    when(mockConfiguration.getProperty(\"jib.test.auth.pass\")).thenReturn(Optional.empty());\n    expected = Credential.from(\"vwxyz\", \"98765\");\n    actual =\n        ConfigurationPropertyValidator.getImageCredential(\n            mockLogger, \"jib.test.auth.user\", \"jib.test.auth.pass\", mockAuth, mockConfiguration);\n    assertThat(actual).hasValue(expected);\n    verify(mockLogger, never()).accept(LogEvent.warn(any()));\n\n    // Auth completely missing\n    when(mockAuth.getUsername()).thenReturn(null);\n    when(mockAuth.getPassword()).thenReturn(null);\n    actual =\n        ConfigurationPropertyValidator.getImageCredential(\n            mockLogger, \"jib.test.auth.user\", \"jib.test.auth.pass\", mockAuth, mockConfiguration);\n    assertThat(actual).isEmpty();\n\n    // Password missing\n    when(mockAuth.getUsername()).thenReturn(\"vwxyz\");\n    when(mockAuth.getPassword()).thenReturn(null);\n    actual =\n        ConfigurationPropertyValidator.getImageCredential(\n            mockLogger, \"jib.test.auth.user\", \"jib.test.auth.pass\", mockAuth, mockConfiguration);\n    assertThat(actual).isEmpty();\n    verify(mockLogger)\n        .accept(LogEvent.warn(\"pass is missing from build configuration; ignoring auth section.\"));\n\n    // Username missing\n    when(mockAuth.getUsername()).thenReturn(null);\n    when(mockAuth.getPassword()).thenReturn(\"98765\");\n    actual =\n        ConfigurationPropertyValidator.getImageCredential(\n            mockLogger, \"jib.test.auth.user\", \"jib.test.auth.pass\", mockAuth, mockConfiguration);\n    assertThat(actual).isEmpty();\n    verify(mockLogger)\n        .accept(LogEvent.warn(\"user is missing from build configuration; ignoring auth section.\"));\n  }\n\n  @Test\n  public void testGetGeneratedTargetDockerTag() throws InvalidImageReferenceException {\n    HelpfulSuggestions helpfulSuggestions =\n        new HelpfulSuggestions(\"\", \"\", \"to\", \"--to\", \"build.txt\");\n\n    // Target configured\n    ProjectProperties mockProjectProperties = mock(ProjectProperties.class);\n    when(mockProjectProperties.getName()).thenReturn(\"project-name\");\n    when(mockProjectProperties.getVersion()).thenReturn(\"project-version\");\n\n    ImageReference result =\n        ConfigurationPropertyValidator.getGeneratedTargetDockerTag(\n            \"a/b:c\", mockProjectProperties, helpfulSuggestions);\n    assertThat(result.getRepository()).isEqualTo(\"a/b\");\n    assertThat(result.getTag()).hasValue(\"c\");\n    verify(mockLogger, never()).accept(LogEvent.lifecycle(any()));\n\n    // Target not configured\n    result =\n        ConfigurationPropertyValidator.getGeneratedTargetDockerTag(\n            null, mockProjectProperties, helpfulSuggestions);\n    assertThat(result.getRepository()).isEqualTo(\"project-name\");\n    assertThat(result.getTag()).hasValue(\"project-version\");\n    verify(mockProjectProperties)\n        .log(\n            LogEvent.lifecycle(\n                \"Tagging image with generated image reference project-name:project-version. If you'd \"\n                    + \"like to specify a different tag, you can set the to parameter in your \"\n                    + \"build.txt, or use the --to=<MY IMAGE> commandline flag.\"));\n\n    // Generated tag invalid\n    when(mockProjectProperties.getName()).thenReturn(\"%#&///*@(\");\n    when(mockProjectProperties.getVersion()).thenReturn(\"%$#//&*@($\");\n    assertThrows(\n        InvalidImageReferenceException.class,\n        () ->\n            ConfigurationPropertyValidator.getGeneratedTargetDockerTag(\n                null, mockProjectProperties, helpfulSuggestions));\n  }\n\n  @Test\n  public void testParseListProperty() {\n    assertThat(ConfigurationPropertyValidator.parseListProperty(\"abc\")).containsExactly(\"abc\");\n    assertThat(\n            ConfigurationPropertyValidator.parseListProperty(\n                \"abcd,efg\\\\,hi\\\\\\\\,,\\\\jkl\\\\,,\\\\\\\\\\\\,mnop,\"))\n        .containsExactly(\"abcd\", \"efg,hi\\\\,\", \"\\\\jkl,\", \"\\\\\\\\,mnop\", \"\")\n        .inOrder();\n    assertThat(ConfigurationPropertyValidator.parseListProperty(\"\")).containsExactly(\"\");\n\n    assertThat(\n            ConfigurationPropertyValidator.parseListProperty(\n                \"-Xmx2g,-agentlib:jdwp=transport=dt_socket\\\\,server=y\\\\,address=*:5005\"))\n        .containsExactly(\"-Xmx2g\", \"-agentlib:jdwp=transport=dt_socket,server=y,address=*:5005\")\n        .inOrder();\n  }\n\n  @Test\n  public void testParseMapProperty() {\n    assertThat(ConfigurationPropertyValidator.parseMapProperty(\"abc=def\"))\n        .containsExactly(\"abc\", \"def\");\n    assertThat(\n            ConfigurationPropertyValidator.parseMapProperty(\n                \"abc=def,gh\\\\,i=j\\\\\\\\\\\\,kl,mno=,pqr=stu\"))\n        .containsExactly(\"abc\", \"def\", \"gh,i\", \"j\\\\\\\\,kl\", \"mno\", \"\", \"pqr\", \"stu\")\n        .inOrder();\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> ConfigurationPropertyValidator.parseMapProperty(\"not valid\"));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ContainerizingModeTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ContainerizingMode}. */\npublic class ContainerizingModeTest {\n\n  @Test\n  public void testFrom_validValues() throws InvalidContainerizingModeException {\n    Assert.assertEquals(ContainerizingMode.EXPLODED, ContainerizingMode.from(\"exploded\"));\n    Assert.assertEquals(ContainerizingMode.PACKAGED, ContainerizingMode.from(\"packaged\"));\n  }\n\n  @Test\n  public void testFrom_invalidCasing() {\n    try {\n      ContainerizingMode.from(\"PACKAGED\");\n      Assert.fail();\n    } catch (InvalidContainerizingModeException ex) {\n      Assert.assertEquals(\"PACKAGED\", ex.getInvalidContainerizingMode());\n      Assert.assertEquals(\"PACKAGED\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testFrom_invalidValue() {\n    try {\n      ContainerizingMode.from(\"this is wrong\");\n      Assert.fail();\n    } catch (InvalidContainerizingModeException ex) {\n      Assert.assertEquals(\"this is wrong\", ex.getInvalidContainerizingMode());\n      Assert.assertEquals(\"this is wrong\", ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrieversTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.Credential;\nimport com.google.cloud.tools.jib.api.CredentialRetriever;\nimport com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory;\nimport com.google.common.collect.ImmutableMap;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link DefaultCredentialRetrievers}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class DefaultCredentialRetrieversTest {\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock private CredentialRetrieverFactory mockCredentialRetrieverFactory;\n  @Mock private CredentialRetriever mockDockerCredentialHelperCredentialRetriever;\n  @Mock private CredentialRetriever mockKnownCredentialRetriever;\n  @Mock private CredentialRetriever mockInferredCredentialRetriever;\n  @Mock private CredentialRetriever mockWellKnownCredentialHelpersCredentialRetriever;\n  @Mock private CredentialRetriever mockXdgPrimaryCredentialRetriever;\n  @Mock private CredentialRetriever mockEnvHomeXdgCredentialRetriever;\n  @Mock private CredentialRetriever mockSystemHomeXdgCredentialRetriever;\n  @Mock private CredentialRetriever mockDockerConfigEnvDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockDockerConfigEnvLegacyDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockSystemHomeDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockSystemHomeKubernetesDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockSystemHomeLegacyDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockEnvHomeDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockEnvHomeKubernetesDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockEnvHomeLegacyDockerConfigCredentialRetriever;\n  @Mock private CredentialRetriever mockApplicationDefaultCredentialRetriever;\n\n  private Properties properties;\n  private Map<String, String> environment;\n\n  private final Credential knownCredential = Credential.from(\"username\", \"password\");\n  private final Credential inferredCredential = Credential.from(\"username2\", \"password2\");\n\n  @Before\n  public void setUp() {\n    properties = new Properties();\n    properties.setProperty(\"os.name\", \"unknown\");\n    properties.setProperty(\"user.home\", Paths.get(\"/system/home\").toString());\n    environment =\n        ImmutableMap.of(\n            \"HOME\",\n            Paths.get(\"/env/home\").toString(),\n            \"DOCKER_CONFIG\",\n            Paths.get(\"/docker_config\").toString(),\n            \"XDG_RUNTIME_DIR\",\n            Paths.get(\"/run/user/1000\").toString(),\n            \"XDG_CONFIG_HOME\",\n            Paths.get(\"/env/home/.config\").toString());\n\n    when(mockCredentialRetrieverFactory.dockerCredentialHelper(anyString()))\n        .thenReturn(mockDockerCredentialHelperCredentialRetriever);\n    when(mockCredentialRetrieverFactory.known(knownCredential, \"credentialSource\"))\n        .thenReturn(mockKnownCredentialRetriever);\n    when(mockCredentialRetrieverFactory.known(inferredCredential, \"inferredCredentialSource\"))\n        .thenReturn(mockInferredCredentialRetriever);\n    when(mockCredentialRetrieverFactory.wellKnownCredentialHelpers())\n        .thenReturn(mockWellKnownCredentialHelpersCredentialRetriever);\n\n    when(mockCredentialRetrieverFactory.dockerConfig(\n            Paths.get(\"/run/user/1000/containers/auth.json\")))\n        .thenReturn(mockXdgPrimaryCredentialRetriever);\n    when(mockCredentialRetrieverFactory.dockerConfig(\n            Paths.get(\"/env/home/.config/containers/auth.json\")))\n        .thenReturn(mockEnvHomeXdgCredentialRetriever);\n\n    when(mockCredentialRetrieverFactory.dockerConfig(\n            Paths.get(\"/system/home/.config/containers/auth.json\")))\n        .thenReturn(mockSystemHomeXdgCredentialRetriever);\n\n    when(mockCredentialRetrieverFactory.dockerConfig(Paths.get(\"/docker_config/config.json\")))\n        .thenReturn(mockDockerConfigEnvDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.dockerConfig(Paths.get(\"/docker_config/.dockerconfigjson\")))\n        .thenReturn(mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.legacyDockerConfig(Paths.get(\"/docker_config/.dockercfg\")))\n        .thenReturn(mockDockerConfigEnvLegacyDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.dockerConfig(Paths.get(\"/system/home/.docker/config.json\")))\n        .thenReturn(mockSystemHomeDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.dockerConfig(\n            Paths.get(\"/system/home/.docker/.dockerconfigjson\")))\n        .thenReturn(mockSystemHomeKubernetesDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.legacyDockerConfig(\n            Paths.get(\"/system/home/.docker/.dockercfg\")))\n        .thenReturn(mockSystemHomeLegacyDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.dockerConfig(Paths.get(\"/env/home/.docker/config.json\")))\n        .thenReturn(mockEnvHomeDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.dockerConfig(\n            Paths.get(\"/env/home/.docker/.dockerconfigjson\")))\n        .thenReturn(mockEnvHomeKubernetesDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.legacyDockerConfig(\n            Paths.get(\"/env/home/.docker/.dockercfg\")))\n        .thenReturn(mockEnvHomeLegacyDockerConfigCredentialRetriever);\n    when(mockCredentialRetrieverFactory.googleApplicationDefaultCredentials())\n        .thenReturn(mockApplicationDefaultCredentialRetriever);\n  }\n\n  @Test\n  public void testAsList() throws FileNotFoundException {\n    List<CredentialRetriever> retriever =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .asList();\n    assertThat(retriever)\n        .containsExactly(\n            mockXdgPrimaryCredentialRetriever,\n            mockEnvHomeXdgCredentialRetriever,\n            mockSystemHomeXdgCredentialRetriever,\n            mockDockerConfigEnvDockerConfigCredentialRetriever,\n            mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever,\n            mockDockerConfigEnvLegacyDockerConfigCredentialRetriever,\n            mockSystemHomeDockerConfigCredentialRetriever,\n            mockSystemHomeKubernetesDockerConfigCredentialRetriever,\n            mockSystemHomeLegacyDockerConfigCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n  }\n\n  @Test\n  public void testAsList_all() throws FileNotFoundException {\n    List<CredentialRetriever> retrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .setKnownCredential(knownCredential, \"credentialSource\")\n            .setInferredCredential(inferredCredential, \"inferredCredentialSource\")\n            .setCredentialHelper(\"credentialHelperSuffix\")\n            .asList();\n    assertThat(retrievers)\n        .containsExactly(\n            mockKnownCredentialRetriever,\n            mockDockerCredentialHelperCredentialRetriever,\n            mockInferredCredentialRetriever,\n            mockXdgPrimaryCredentialRetriever,\n            mockEnvHomeXdgCredentialRetriever,\n            mockSystemHomeXdgCredentialRetriever,\n            mockDockerConfigEnvDockerConfigCredentialRetriever,\n            mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever,\n            mockDockerConfigEnvLegacyDockerConfigCredentialRetriever,\n            mockSystemHomeDockerConfigCredentialRetriever,\n            mockSystemHomeKubernetesDockerConfigCredentialRetriever,\n            mockSystemHomeLegacyDockerConfigCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n\n    verify(mockCredentialRetrieverFactory).known(knownCredential, \"credentialSource\");\n    verify(mockCredentialRetrieverFactory).known(inferredCredential, \"inferredCredentialSource\");\n    verify(mockCredentialRetrieverFactory)\n        .dockerCredentialHelper(\"docker-credential-credentialHelperSuffix\");\n  }\n\n  @Test\n  public void testAsList_credentialHelperPath() throws IOException {\n    Path fakeCredentialHelperPath = temporaryFolder.newFile(\"fake-credHelper\").toPath();\n    DefaultCredentialRetrievers credentialRetrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .setCredentialHelper(fakeCredentialHelperPath.toString());\n\n    List<CredentialRetriever> retrievers = credentialRetrievers.asList();\n    assertThat(retrievers)\n        .containsExactly(\n            mockDockerCredentialHelperCredentialRetriever,\n            mockXdgPrimaryCredentialRetriever,\n            mockEnvHomeXdgCredentialRetriever,\n            mockSystemHomeXdgCredentialRetriever,\n            mockDockerConfigEnvDockerConfigCredentialRetriever,\n            mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever,\n            mockDockerConfigEnvLegacyDockerConfigCredentialRetriever,\n            mockSystemHomeDockerConfigCredentialRetriever,\n            mockSystemHomeKubernetesDockerConfigCredentialRetriever,\n            mockSystemHomeLegacyDockerConfigCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n    verify(mockCredentialRetrieverFactory)\n        .dockerCredentialHelper(fakeCredentialHelperPath.toString());\n\n    Files.delete(fakeCredentialHelperPath);\n    Exception ex = assertThrows(FileNotFoundException.class, credentialRetrievers::asList);\n    assertThat(ex)\n        .hasMessageThat()\n        .isEqualTo(\"Specified credential helper was not found: \" + fakeCredentialHelperPath);\n  }\n\n  @Test\n  public void testDockerConfigRetrievers_undefinedHome() throws FileNotFoundException {\n    List<CredentialRetriever> retrievers =\n        new DefaultCredentialRetrievers(\n                mockCredentialRetrieverFactory, new Properties(), new HashMap<>())\n            .asList();\n    assertThat(retrievers)\n        .containsExactly(\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n  }\n\n  @Test\n  public void testDockerConfigRetrievers_noDuplicateRetrievers() throws FileNotFoundException {\n    properties.setProperty(\"user.home\", Paths.get(\"/env/home\").toString());\n    List<CredentialRetriever> retrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .asList();\n    assertThat(retrievers)\n        .containsExactly(\n            mockXdgPrimaryCredentialRetriever,\n            mockEnvHomeXdgCredentialRetriever,\n            mockDockerConfigEnvDockerConfigCredentialRetriever,\n            mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever,\n            mockDockerConfigEnvLegacyDockerConfigCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n\n    environment =\n        ImmutableMap.of(\n            \"HOME\",\n            Paths.get(\"/env/home\").toString(),\n            \"DOCKER_CONFIG\",\n            Paths.get(\"/env/home/.docker\").toString());\n    retrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .asList();\n    assertThat(retrievers)\n        .containsExactly(\n            mockEnvHomeXdgCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n  }\n\n  @Test\n  public void testCredentialHelper_cmdExtension() throws IOException {\n    Path credHelper = temporaryFolder.newFile(\"foo.cmd\").toPath();\n    Path pathWithoutCmd = credHelper.getParent().resolve(\"foo\");\n    assertThat(credHelper).isEqualTo(pathWithoutCmd.getParent().resolve(\"foo.cmd\"));\n\n    DefaultCredentialRetrievers credentialRetrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .setCredentialHelper(pathWithoutCmd.toString());\n    Exception ex = assertThrows(FileNotFoundException.class, credentialRetrievers::asList);\n    assertThat(ex).hasMessageThat().startsWith(\"Specified credential helper was not found:\");\n    assertThat(ex).hasMessageThat().endsWith(\"foo\");\n\n    properties.setProperty(\"os.name\", \"winDOWs\");\n    List<CredentialRetriever> retrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .setCredentialHelper(pathWithoutCmd.toString())\n            .asList();\n\n    assertThat(retrievers)\n        .containsExactly(\n            mockDockerCredentialHelperCredentialRetriever,\n            mockXdgPrimaryCredentialRetriever,\n            mockEnvHomeXdgCredentialRetriever,\n            mockSystemHomeXdgCredentialRetriever,\n            mockDockerConfigEnvDockerConfigCredentialRetriever,\n            mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever,\n            mockDockerConfigEnvLegacyDockerConfigCredentialRetriever,\n            mockSystemHomeDockerConfigCredentialRetriever,\n            mockSystemHomeKubernetesDockerConfigCredentialRetriever,\n            mockSystemHomeLegacyDockerConfigCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n  }\n\n  @Test\n  public void testCredentialHelper_exeExtension() throws IOException {\n    Path credHelper = temporaryFolder.newFile(\"foo.exe\").toPath();\n    Path pathWithoutExe = credHelper.getParent().resolve(\"foo\");\n    assertThat(credHelper).isEqualTo(pathWithoutExe.getParent().resolve(\"foo.exe\"));\n\n    DefaultCredentialRetrievers credentialRetrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .setCredentialHelper(pathWithoutExe.toString());\n    Exception ex = assertThrows(FileNotFoundException.class, credentialRetrievers::asList);\n    assertThat(ex).hasMessageThat().startsWith(\"Specified credential helper was not found:\");\n    assertThat(ex).hasMessageThat().endsWith(\"foo\");\n\n    properties.setProperty(\"os.name\", \"winDOWs\");\n    List<CredentialRetriever> retrievers =\n        new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment)\n            .setCredentialHelper(pathWithoutExe.toString())\n            .asList();\n\n    assertThat(retrievers)\n        .containsExactly(\n            mockDockerCredentialHelperCredentialRetriever,\n            mockXdgPrimaryCredentialRetriever,\n            mockEnvHomeXdgCredentialRetriever,\n            mockSystemHomeXdgCredentialRetriever,\n            mockDockerConfigEnvDockerConfigCredentialRetriever,\n            mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever,\n            mockDockerConfigEnvLegacyDockerConfigCredentialRetriever,\n            mockSystemHomeDockerConfigCredentialRetriever,\n            mockSystemHomeKubernetesDockerConfigCredentialRetriever,\n            mockSystemHomeLegacyDockerConfigCredentialRetriever,\n            mockEnvHomeDockerConfigCredentialRetriever,\n            mockEnvHomeKubernetesDockerConfigCredentialRetriever,\n            mockEnvHomeLegacyDockerConfigCredentialRetriever,\n            mockWellKnownCredentialHelpersCredentialRetriever,\n            mockApplicationDefaultCredentialRetriever)\n        .inOrder();\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/HelpfulSuggestionsTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link HelpfulSuggestions}. */\npublic class HelpfulSuggestionsTest {\n\n  private static final HelpfulSuggestions TEST_HELPFUL_SUGGESTIONS =\n      new HelpfulSuggestions(\n          \"messagePrefix\", \"clearCacheCommand\", \"toProperty\", \"toFlag\", \"buildFile\");\n\n  @Test\n  public void testSuggestions_smoke() {\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should make sure your Internet is up and that the registry you are pushing to exists\",\n        TEST_HELPFUL_SUGGESTIONS.forHttpHostConnect());\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should make sure that the registry you configured exists/is spelled properly\",\n        TEST_HELPFUL_SUGGESTIONS.forUnknownHost());\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should run 'clearCacheCommand' to clear your build cache\",\n        TEST_HELPFUL_SUGGESTIONS.forCacheNeedsClean());\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should check that 'cacheDirectory' is not used by another application or set the `jib.useOnlyProjectCache` system property\",\n        TEST_HELPFUL_SUGGESTIONS.forCacheDirectoryNotOwned(Paths.get(\"cacheDirectory\")));\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should make sure you have permissions for imageReference and set correct credentials. See https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-forbidden-or-denied for help\",\n        TEST_HELPFUL_SUGGESTIONS.forHttpStatusCodeForbidden(\"imageReference\"));\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should make sure your credentials for 'registry/repository' are set up correctly. See https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-unauthorized for help\",\n        TEST_HELPFUL_SUGGESTIONS.forNoCredentialsDefined(\"registry/repository\"));\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should add a `mainClass` configuration to plugin\",\n        HelpfulSuggestions.forMainClassNotFound(\"messagePrefix\", \"plugin\"));\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should add a parameter configuration parameter to your buildFile or set the parameter via the commandline (e.g. 'command').\",\n        HelpfulSuggestions.forToNotConfigured(\n            \"messagePrefix\", \"parameter\", \"buildFile\", \"command\"));\n    Assert.assertEquals(\n        \"Your project is using Java 11 but the base image is for Java 8, perhaps you should \"\n            + \"configure a Java 11-compatible base image using the 'jib.from.image' \"\n            + \"parameter, or set targetCompatibility = 8 or below in your build \"\n            + \"configuration\",\n        HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForGradle(8, 11));\n    Assert.assertEquals(\n        \"Your project is using Java 11 but the base image is for Java 8, perhaps you should \"\n            + \"configure a Java 11-compatible base image using the '<from><image>' \"\n            + \"parameter, or set maven-compiler-plugin's '<target>' or '<release>' version \"\n            + \"to 8 or below in your build configuration\",\n        HelpfulSuggestions.forIncompatibleBaseImageJavaVersionForMaven(8, 11));\n    Assert.assertEquals(\n        \"Invalid image reference gcr.io/invalid_REF, perhaps you should check that the reference \"\n            + \"is formatted correctly according to https://docs.docker.com/engine/reference/commandline/tag/#extended-description\\n\"\n            + \"For example, slash-separated name components cannot have uppercase letters\",\n        HelpfulSuggestions.forInvalidImageReference(\"gcr.io/invalid_REF\"));\n    Assert.assertEquals(\"messagePrefix\", TEST_HELPFUL_SUGGESTIONS.none());\n    Assert.assertEquals(\n        \"messagePrefix, perhaps you should use a registry that supports HTTPS so credentials can be sent safely, or set the 'sendCredentialsOverHttp' system property to true\",\n        TEST_HELPFUL_SUGGESTIONS.forCredentialsNotSent());\n    Assert.assertEquals(\n        \"Tagging image with generated image reference project-name:project-version. If you'd like to specify a different tag, you can set the toProperty parameter in your buildFile, or use the toFlag=<MY IMAGE> commandline flag.\",\n        TEST_HELPFUL_SUGGESTIONS.forGeneratedTag(\"project-name\", \"project-version\"));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport org.junit.Assert;\nimport org.junit.Test;\n\npublic class ImageMetadataOutputTest {\n\n  private static final String TEST_JSON =\n      \"{\\\"image\\\":\"\n          + \"\\\"gcr.io/project/image:tag\\\",\"\n          + \"\\\"imageId\\\":\"\n          + \"\\\"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\\\",\"\n          + \"\\\"imageDigest\\\":\"\n          + \"\\\"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc\\\",\"\n          + \"\\\"tags\\\":[\\\"latest\\\",\\\"tag\\\"],\"\n          + \"\\\"imagePushed\\\":true\"\n          + \"}\";\n\n  @Test\n  public void testFromJson() throws IOException {\n    ImageMetadataOutput output = ImageMetadataOutput.fromJson(TEST_JSON);\n    Assert.assertEquals(\"gcr.io/project/image:tag\", output.getImage());\n    Assert.assertEquals(\n        \"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\",\n        output.getImageId());\n    Assert.assertEquals(\n        \"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc\",\n        output.getImageDigest());\n    Assert.assertTrue(output.isImagePushed());\n\n    Assert.assertEquals(ImmutableList.of(\"latest\", \"tag\"), output.getTags());\n  }\n\n  @Test\n  public void testToJson() throws IOException {\n    ImageMetadataOutput output = ImageMetadataOutput.fromJson(TEST_JSON);\n    Assert.assertEquals(TEST_JSON, output.toJson());\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JavaContainerBuilderHelperTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport static com.google.common.truth.Truth.assertThat;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder.LayerType;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.configuration.BuildContext;\nimport com.google.cloud.tools.jib.filesystem.FileOperations;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.Resources;\nimport com.google.common.truth.Correspondence;\nimport com.google.common.util.concurrent.MoreExecutors;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link JavaContainerBuilderHelper}. */\npublic class JavaContainerBuilderHelperTest {\n\n  private static final Correspondence<FileEntry, Path> SOURCE_FILE_OF =\n      Correspondence.transforming(FileEntry::getSourceFile, \"has sourceFile of\");\n  private static final Correspondence<FileEntry, String> EXTRACTION_PATH_OF =\n      Correspondence.transforming(\n          entry -> entry.getExtractionPath().toString(), \"has extractionPath of\");\n\n  private static FileEntriesLayer getLayerConfigurationByName(\n      BuildContext buildContext, String name) {\n    return buildContext.getLayerConfigurations().stream()\n        .filter(layer -> layer.getName().equals(name))\n        .findFirst()\n        .get();\n  }\n\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration() throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    FileEntriesLayer layerConfiguration =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Collections.emptyList(),\n            Collections.emptyList(),\n            Collections.emptyMap(),\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(layerConfiguration.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            extraFilesDirectory.resolve(\"a\"),\n            extraFilesDirectory.resolve(\"a/b\"),\n            extraFilesDirectory.resolve(\"a/b/bar\"),\n            extraFilesDirectory.resolve(\"c\"),\n            extraFilesDirectory.resolve(\"c/cat\"),\n            extraFilesDirectory.resolve(\"foo\"));\n  }\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration_includes()\n      throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    FileEntriesLayer layerConfiguration =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Arrays.asList(\"**/bar\", \"**/*a*\"),\n            Collections.emptyList(),\n            Collections.emptyMap(),\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(layerConfiguration.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            extraFilesDirectory.resolve(\"a/b/bar\"), extraFilesDirectory.resolve(\"c/cat\"));\n  }\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration_excludes()\n      throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    FileEntriesLayer layerConfiguration =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Collections.emptyList(),\n            Arrays.asList(\"**/bar\", \"**/*a*\"),\n            Collections.emptyMap(),\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(layerConfiguration.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            extraFilesDirectory.resolve(\"a\"),\n            extraFilesDirectory.resolve(\"a/b\"),\n            extraFilesDirectory.resolve(\"c\"),\n            extraFilesDirectory.resolve(\"foo\"));\n  }\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration_includesAndExcludesEverything()\n      throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    FileEntriesLayer layerConfiguration =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Arrays.asList(\"**/*\"),\n            Arrays.asList(\"**/*\"),\n            Collections.emptyMap(),\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(layerConfiguration.getEntries()).isEmpty();\n  }\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration_includesAndExcludes()\n      throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    FileEntriesLayer layerConfiguration =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Arrays.asList(\"**/*a*\", \"a\"),\n            Arrays.asList(\"**/*c*\"),\n            Collections.emptyMap(),\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(layerConfiguration.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(extraFilesDirectory.resolve(\"a\"), extraFilesDirectory.resolve(\"a/b/bar\"));\n  }\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration_globPermissions()\n      throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    Map<String, FilePermissions> permissionsMap =\n        ImmutableMap.of(\n            \"/a\",\n            FilePermissions.fromOctalString(\"123\"),\n            \"/a/*\",\n            FilePermissions.fromOctalString(\"456\"),\n            \"**/bar\",\n            FilePermissions.fromOctalString(\"765\"));\n    FileEntriesLayer fileEntriesLayer =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Collections.emptyList(),\n            Collections.emptyList(),\n            permissionsMap,\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(fileEntriesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/a\", \"/a/b\", \"/a/b/bar\", \"/c\", \"/c/cat\", \"/foo\");\n\n    Map<AbsoluteUnixPath, FilePermissions> expectedPermissions =\n        ImmutableMap.<AbsoluteUnixPath, FilePermissions>builder()\n            .put(AbsoluteUnixPath.get(\"/a\"), FilePermissions.fromOctalString(\"123\"))\n            .put(AbsoluteUnixPath.get(\"/a/b\"), FilePermissions.fromOctalString(\"456\"))\n            .put(AbsoluteUnixPath.get(\"/a/b/bar\"), FilePermissions.fromOctalString(\"765\"))\n            .put(AbsoluteUnixPath.get(\"/c\"), FilePermissions.DEFAULT_FOLDER_PERMISSIONS)\n            .put(AbsoluteUnixPath.get(\"/c/cat\"), FilePermissions.DEFAULT_FILE_PERMISSIONS)\n            .put(AbsoluteUnixPath.get(\"/foo\"), FilePermissions.DEFAULT_FILE_PERMISSIONS)\n            .build();\n    for (FileEntry entry : fileEntriesLayer.getEntries()) {\n      assertThat(entry.getPermissions())\n          .isEqualTo(expectedPermissions.get(entry.getExtractionPath()));\n    }\n  }\n\n  @Test\n  public void testExtraDirectoryLayerConfiguration_overlappingPermissions()\n      throws URISyntaxException, IOException {\n    Path extraFilesDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    Map<String, FilePermissions> permissionsMap =\n        ImmutableMap.of(\n            \"/a**\",\n            FilePermissions.fromOctalString(\"123\"),\n            // Should be ignored, since first match takes priority\n            \"/a/b**\",\n            FilePermissions.fromOctalString(\"000\"),\n            // Should override first match since explicit path is used instead of glob\n            \"/a/b/bar\",\n            FilePermissions.fromOctalString(\"765\"));\n    FileEntriesLayer fileEntriesLayer =\n        JavaContainerBuilderHelper.extraDirectoryLayerConfiguration(\n            extraFilesDirectory,\n            AbsoluteUnixPath.get(\"/\"),\n            Collections.emptyList(),\n            Collections.emptyList(),\n            permissionsMap,\n            (ignored1, ignored2) -> Instant.EPOCH);\n    assertThat(fileEntriesLayer.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/a\", \"/a/b\", \"/a/b/bar\", \"/c\", \"/c/cat\", \"/foo\");\n\n    Map<AbsoluteUnixPath, FilePermissions> expectedPermissions =\n        ImmutableMap.<AbsoluteUnixPath, FilePermissions>builder()\n            .put(AbsoluteUnixPath.get(\"/a\"), FilePermissions.fromOctalString(\"123\"))\n            .put(AbsoluteUnixPath.get(\"/a/b\"), FilePermissions.fromOctalString(\"123\"))\n            .put(AbsoluteUnixPath.get(\"/a/b/bar\"), FilePermissions.fromOctalString(\"765\"))\n            .put(AbsoluteUnixPath.get(\"/c\"), FilePermissions.DEFAULT_FOLDER_PERMISSIONS)\n            .put(AbsoluteUnixPath.get(\"/c/cat\"), FilePermissions.DEFAULT_FILE_PERMISSIONS)\n            .put(AbsoluteUnixPath.get(\"/foo\"), FilePermissions.DEFAULT_FILE_PERMISSIONS)\n            .build();\n    for (FileEntry entry : fileEntriesLayer.getEntries()) {\n      assertThat(entry.getPermissions())\n          .isEqualTo(expectedPermissions.get(entry.getExtractionPath()));\n    }\n  }\n\n  @Test\n  public void testFromExplodedWar()\n      throws URISyntaxException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    // Copy test files to a temporary directory that we can safely operate on\n    Path resourceExplodedWar =\n        Paths.get(Resources.getResource(\"plugins-common/exploded-war\").toURI());\n    FileOperations.copy(ImmutableList.of(resourceExplodedWar), temporaryFolder.getRoot().toPath());\n    Path temporaryExplodedWar = temporaryFolder.getRoot().toPath().resolve(\"exploded-war\");\n    Files.createDirectories(temporaryExplodedWar.resolve(\"WEB-INF/classes/empty_dir\"));\n    Files.createFile(temporaryExplodedWar.resolve(\"WEB-INF/lib/project-dependency-1.0.0.jar\"));\n    Set<String> projectArtifacts = ImmutableSet.of(\"project-dependency-1.0.0.jar\");\n\n    JavaContainerBuilder javaContainerBuilder =\n        JavaContainerBuilder.from(RegistryImage.named(\"base\"))\n            .setAppRoot(AbsoluteUnixPath.get(\"/my/app\"));\n    JibContainerBuilder jibContainerBuilder =\n        JavaContainerBuilderHelper.fromExplodedWar(\n            javaContainerBuilder, temporaryExplodedWar, projectArtifacts);\n    BuildContext buildContext =\n        JibContainerBuilderTestHelper.toBuildContext(\n            jibContainerBuilder,\n            Containerizer.to(RegistryImage.named(\"target\"))\n                .setExecutorService(MoreExecutors.newDirectExecutorService()));\n\n    FileEntriesLayer resourcesLayerConfigurations =\n        getLayerConfigurationByName(buildContext, LayerType.RESOURCES.getName());\n    FileEntriesLayer classesLayerConfigurations =\n        getLayerConfigurationByName(buildContext, LayerType.CLASSES.getName());\n    FileEntriesLayer dependenciesLayerConfigurations =\n        getLayerConfigurationByName(buildContext, LayerType.DEPENDENCIES.getName());\n    FileEntriesLayer snapshotsLayerConfigurations =\n        getLayerConfigurationByName(buildContext, LayerType.SNAPSHOT_DEPENDENCIES.getName());\n    FileEntriesLayer projectDependenciesLayerConfigurations =\n        getLayerConfigurationByName(buildContext, LayerType.PROJECT_DEPENDENCIES.getName());\n\n    assertThat(projectDependenciesLayerConfigurations.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(temporaryExplodedWar.resolve(\"WEB-INF/lib/project-dependency-1.0.0.jar\"));\n    assertThat(dependenciesLayerConfigurations.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(temporaryExplodedWar.resolve(\"WEB-INF/lib/dependency-1.0.0.jar\"));\n    assertThat(snapshotsLayerConfigurations.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            temporaryExplodedWar.resolve(\"WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\"));\n    assertThat(resourcesLayerConfigurations.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            temporaryExplodedWar.resolve(\"META-INF\"),\n            temporaryExplodedWar.resolve(\"META-INF/context.xml\"),\n            temporaryExplodedWar.resolve(\"Test.jsp\"),\n            temporaryExplodedWar.resolve(\"WEB-INF\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/empty_dir\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/package\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/package/test.properties\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/lib\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/web.xml\"));\n    assertThat(classesLayerConfigurations.getEntries())\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/HelloWorld.class\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/empty_dir\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/package\"),\n            temporaryExplodedWar.resolve(\"WEB-INF/classes/package/Other.class\"));\n\n    assertThat(dependenciesLayerConfigurations.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependency-1.0.0.jar\");\n    assertThat(snapshotsLayerConfigurations.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/my/app/WEB-INF/lib/dependencyX-1.0.0-SNAPSHOT.jar\");\n    assertThat(resourcesLayerConfigurations.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/META-INF\",\n            \"/my/app/META-INF/context.xml\",\n            \"/my/app/Test.jsp\",\n            \"/my/app/WEB-INF\",\n            \"/my/app/WEB-INF/classes\",\n            \"/my/app/WEB-INF/classes/empty_dir\",\n            \"/my/app/WEB-INF/classes/package\",\n            \"/my/app/WEB-INF/classes/package/test.properties\",\n            \"/my/app/WEB-INF/lib\",\n            \"/my/app/WEB-INF/web.xml\");\n    assertThat(classesLayerConfigurations.getEntries())\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/my/app/WEB-INF/classes/HelloWorld.class\",\n            \"/my/app/WEB-INF/classes/empty_dir\",\n            \"/my/app/WEB-INF/classes/package\",\n            \"/my/app/WEB-INF/classes/package/Other.class\");\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.api.client.http.HttpResponseException;\nimport com.google.api.client.http.HttpStatusCodes;\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.api.InsecureRegistryException;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.RegistryException;\nimport com.google.cloud.tools.jib.api.RegistryUnauthorizedException;\nimport com.google.cloud.tools.jib.registry.RegistryCredentialsNotSentException;\nimport com.google.common.collect.ImmutableSet;\nimport java.io.IOException;\nimport java.net.UnknownHostException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Set;\nimport java.util.concurrent.ExecutionException;\nimport org.apache.http.conn.HttpHostConnectException;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link JibBuildRunner}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class JibBuildRunnerTest {\n\n  private static final HelpfulSuggestions TEST_HELPFUL_SUGGESTIONS =\n      new HelpfulSuggestions(\n          \"messagePrefix\", \"clearCacheCommand\", \"toConfig\", \"toFlag\", \"buildFile\");\n\n  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock private JibContainerBuilder mockJibContainerBuilder;\n  @Mock private JibContainer mockJibContainer;\n  @Mock private Containerizer mockContainerizer;\n  @Mock private RegistryUnauthorizedException mockRegistryUnauthorizedException;\n  @Mock private RegistryCredentialsNotSentException mockRegistryCredentialsNotSentException;\n  @Mock private HttpResponseException mockHttpResponseException;\n\n  private JibBuildRunner testJibBuildRunner;\n\n  @Before\n  public void setUpMocks() {\n    testJibBuildRunner =\n        new JibBuildRunner(\n            mockJibContainerBuilder,\n            mockContainerizer,\n            ignored -> {},\n            TEST_HELPFUL_SUGGESTIONS,\n            \"ignored\",\n            \"ignored\");\n  }\n\n  @Test\n  public void testBuildImage_pass()\n      throws BuildStepsExecutionException, IOException, CacheDirectoryCreationException {\n    JibContainer buildResult = testJibBuildRunner.runBuild();\n    Assert.assertNull(buildResult);\n  }\n\n  @Test\n  public void testBuildImage_httpHostConnectException()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    HttpHostConnectException mockHttpHostConnectException =\n        Mockito.mock(HttpHostConnectException.class);\n    Mockito.doThrow(mockHttpHostConnectException)\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forHttpHostConnect(), ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_unknownHostException()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    UnknownHostException mockUnknownHostException = Mockito.mock(UnknownHostException.class);\n    Mockito.doThrow(mockUnknownHostException)\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forUnknownHost(), ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_insecureRegistryException()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    InsecureRegistryException mockInsecureRegistryException =\n        Mockito.mock(InsecureRegistryException.class);\n    Mockito.doThrow(mockInsecureRegistryException)\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forInsecureRegistry(), ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_registryUnauthorizedException_statusCodeForbidden()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    Mockito.when(mockRegistryUnauthorizedException.getHttpResponseException())\n        .thenReturn(mockHttpResponseException);\n    Mockito.when(mockRegistryUnauthorizedException.getImageReference())\n        .thenReturn(\"someregistry/somerepository\");\n    Mockito.when(mockHttpResponseException.getStatusCode())\n        .thenReturn(HttpStatusCodes.STATUS_CODE_FORBIDDEN);\n\n    Mockito.doThrow(mockRegistryUnauthorizedException)\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(\n          TEST_HELPFUL_SUGGESTIONS.forHttpStatusCodeForbidden(\"someregistry/somerepository\"),\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_registryUnauthorizedException_noCredentials()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    Mockito.when(mockRegistryUnauthorizedException.getHttpResponseException())\n        .thenReturn(mockHttpResponseException);\n    Mockito.when(mockRegistryUnauthorizedException.getImageReference())\n        .thenReturn(\"someregistry/somerepository\");\n    Mockito.when(mockHttpResponseException.getStatusCode()).thenReturn(-1); // Unknown\n\n    Mockito.doThrow(mockRegistryUnauthorizedException)\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(\n          TEST_HELPFUL_SUGGESTIONS.forNoCredentialsDefined(\"someregistry/somerepository\"),\n          ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_registryCredentialsNotSentException()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    Mockito.doThrow(mockRegistryCredentialsNotSentException)\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forCredentialsNotSent(), ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_other()\n      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,\n          ExecutionException {\n    Mockito.doThrow(new RegistryException(\"messagePrefix\"))\n        .when(mockJibContainerBuilder)\n        .containerize(mockContainerizer);\n\n    try {\n      testJibBuildRunner.runBuild();\n      Assert.fail();\n\n    } catch (BuildStepsExecutionException ex) {\n      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.none(), ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testBuildImage_writesImageJson() throws Exception {\n    final ImageReference targetImageReference = ImageReference.parse(\"gcr.io/distroless/java:11\");\n    final String imageId =\n        \"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\";\n    final String digest = \"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc\";\n    final Set<String> tags = ImmutableSet.of(\"latest\", \"0.1.41-69d10e-20200116T101403\");\n\n    final Path outputPath = temporaryFolder.newFile(\"jib-image.json\").toPath();\n\n    Mockito.when(mockJibContainer.getTargetImage()).thenReturn(targetImageReference);\n    Mockito.when(mockJibContainer.getImageId()).thenReturn(DescriptorDigest.fromDigest(imageId));\n    Mockito.when(mockJibContainer.getDigest()).thenReturn(DescriptorDigest.fromDigest(digest));\n    Mockito.when(mockJibContainer.getTags()).thenReturn(tags);\n    Mockito.when(mockJibContainerBuilder.containerize(mockContainerizer))\n        .thenReturn(mockJibContainer);\n    Mockito.when(mockJibContainer.isImagePushed()).thenReturn(true);\n    testJibBuildRunner.writeImageJson(outputPath).runBuild();\n\n    final String outputJson = new String(Files.readAllBytes(outputPath), StandardCharsets.UTF_8);\n    final ImageMetadataOutput metadataOutput = ImageMetadataOutput.fromJson(outputJson);\n    Assert.assertEquals(targetImageReference.toString(), metadataOutput.getImage());\n    Assert.assertEquals(imageId, metadataOutput.getImageId());\n    Assert.assertEquals(digest, metadataOutput.getImageDigest());\n    Assert.assertEquals(tags, ImmutableSet.copyOf(metadataOutput.getTags()));\n    Assert.assertTrue(metadataOutput.isImagePushed());\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/MainClassResolverTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.filesystem.DirectoryWalker;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Paths;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Test for {@link MainClassResolver}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class MainClassResolverTest {\n\n  @Mock private ProjectProperties mockProjectProperties;\n\n  @Before\n  public void setup() {\n    Mockito.when(mockProjectProperties.getPluginName()).thenReturn(\"jib-plugin\");\n    Mockito.when(mockProjectProperties.getJarPluginName()).thenReturn(\"jar-plugin\");\n  }\n\n  @Test\n  public void testResolveMainClass_validMainClassConfigured()\n      throws MainClassInferenceException, IOException {\n    Assert.assertEquals(\n        \"configured.main.class\",\n        MainClassResolver.resolveMainClass(\"configured.main.class\", mockProjectProperties));\n    Mockito.verify(mockProjectProperties, Mockito.never()).log(Mockito.any());\n  }\n\n  @Test\n  public void testResolveMainClass_invalidMainClassConfigured() throws IOException {\n    try {\n      MainClassResolver.resolveMainClass(\"In Val id\", mockProjectProperties);\n      Assert.fail();\n\n    } catch (MainClassInferenceException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"'mainClass' configured in jib-plugin is not a valid Java class: In Val id\"));\n\n      Mockito.verify(mockProjectProperties, Mockito.never()).log(Mockito.any());\n    }\n  }\n\n  @Test\n  public void testResolveMainClass_validMainClassFromJarPlugin()\n      throws MainClassInferenceException, IOException {\n    Mockito.when(mockProjectProperties.getMainClassFromJarPlugin())\n        .thenReturn(\"main.class.from.jar\");\n    Assert.assertEquals(\n        \"main.class.from.jar\", MainClassResolver.resolveMainClass(null, mockProjectProperties));\n\n    String info =\n        \"Searching for main class... Add a 'mainClass' configuration to 'jib-plugin' to \"\n            + \"improve build speed.\";\n    Mockito.verify(mockProjectProperties).log(LogEvent.info(info));\n  }\n\n  @Test\n  public void testResolveMainClass_multipleInferredWithInvalidMainClassFromJarPlugin()\n      throws URISyntaxException, IOException {\n    Mockito.when(mockProjectProperties.getMainClassFromJarPlugin()).thenReturn(\"${start-class}\");\n    Mockito.when(mockProjectProperties.getClassFiles())\n        .thenReturn(\n            new DirectoryWalker(\n                    Paths.get(Resources.getResource(\"core/class-finder-tests/multiple\").toURI()))\n                .walk());\n\n    try {\n      MainClassResolver.resolveMainClass(null, mockProjectProperties);\n      Assert.fail();\n\n    } catch (MainClassInferenceException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Multiple valid main classes were found: HelloWorld, multi.layered.HelloMoon\"));\n\n      String info1 =\n          \"Searching for main class... Add a 'mainClass' configuration to 'jib-plugin' to \"\n              + \"improve build speed.\";\n      String info2 =\n          \"Could not find a valid main class from jar-plugin; looking into all class files to \"\n              + \"infer main class.\";\n      String warn =\n          \"'mainClass' configured in jar-plugin is not a valid Java class: ${start-class}\";\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info1));\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info2));\n      Mockito.verify(mockProjectProperties).log(LogEvent.warn(warn));\n    }\n  }\n\n  @Test\n  public void testResolveMainClass_multipleInferredWithoutMainClassFromJarPlugin()\n      throws URISyntaxException, IOException {\n    Mockito.when(mockProjectProperties.getClassFiles())\n        .thenReturn(\n            new DirectoryWalker(\n                    Paths.get(Resources.getResource(\"core/class-finder-tests/multiple\").toURI()))\n                .walk());\n    try {\n      MainClassResolver.resolveMainClass(null, mockProjectProperties);\n      Assert.fail();\n\n    } catch (MainClassInferenceException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.containsString(\n              \"Multiple valid main classes were found: HelloWorld, multi.layered.HelloMoon\"));\n\n      String info1 =\n          \"Searching for main class... Add a 'mainClass' configuration to 'jib-plugin' to \"\n              + \"improve build speed.\";\n      String info2 =\n          \"Could not find a valid main class from jar-plugin; looking into all class files to \"\n              + \"infer main class.\";\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info1));\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info2));\n    }\n  }\n\n  @Test\n  public void testResolveMainClass_noneInferredWithInvalidMainClassFromJarPlugin()\n      throws IOException {\n    Mockito.when(mockProjectProperties.getMainClassFromJarPlugin()).thenReturn(\"${start-class}\");\n    Mockito.when(mockProjectProperties.getClassFiles())\n        .thenReturn(ImmutableList.of(Paths.get(\"ignored\")));\n    try {\n      MainClassResolver.resolveMainClass(null, mockProjectProperties);\n      Assert.fail();\n\n    } catch (MainClassInferenceException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"Main class was not found\"));\n\n      String info1 =\n          \"Searching for main class... Add a 'mainClass' configuration to 'jib-plugin' to \"\n              + \"improve build speed.\";\n      String info2 =\n          \"Could not find a valid main class from jar-plugin; looking into all class files to \"\n              + \"infer main class.\";\n      String warn =\n          \"'mainClass' configured in jar-plugin is not a valid Java class: ${start-class}\";\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info1));\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info2));\n      Mockito.verify(mockProjectProperties).log(LogEvent.warn(warn));\n    }\n  }\n\n  @Test\n  public void testResolveMainClass_noneInferredWithoutMainClassFromJar() throws IOException {\n    Mockito.when(mockProjectProperties.getClassFiles())\n        .thenReturn(ImmutableList.of(Paths.get(\"ignored\")));\n    try {\n      MainClassResolver.resolveMainClass(null, mockProjectProperties);\n      Assert.fail();\n\n    } catch (MainClassInferenceException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(), CoreMatchers.containsString(\"Main class was not found\"));\n\n      String info1 =\n          \"Searching for main class... Add a 'mainClass' configuration to 'jib-plugin' to \"\n              + \"improve build speed.\";\n      String info2 =\n          \"Could not find a valid main class from jar-plugin; looking into all class files to \"\n              + \"infer main class.\";\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info1));\n      Mockito.verify(mockProjectProperties).log(LogEvent.info(info2));\n    }\n  }\n\n  @Test\n  public void testValidJavaClassRegex() {\n    Assert.assertTrue(MainClassResolver.isValidJavaClass(\"my.Class\"));\n    Assert.assertTrue(MainClassResolver.isValidJavaClass(\"my.java_Class$valid\"));\n    Assert.assertTrue(MainClassResolver.isValidJavaClass(\"multiple.package.items\"));\n    Assert.assertTrue(MainClassResolver.isValidJavaClass(\"is123.valid\"));\n    Assert.assertFalse(MainClassResolver.isValidJavaClass(\"${start-class}\"));\n    Assert.assertFalse(MainClassResolver.isValidJavaClass(\"123not.Valid\"));\n    Assert.assertFalse(MainClassResolver.isValidJavaClass(\"{class}\"));\n    Assert.assertFalse(MainClassResolver.isValidJavaClass(\"not valid\"));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\nimport static org.junit.Assert.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoInteractions;\nimport static org.mockito.Mockito.when;\n\nimport com.google.cloud.tools.jib.api.CacheDirectoryCreationException;\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.InvalidImageReferenceException;\nimport com.google.cloud.tools.jib.api.JavaContainerBuilder;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport com.google.cloud.tools.jib.api.JibContainerBuilderTestHelper;\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.api.RegistryImage;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.ContainerBuildPlan;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport com.google.cloud.tools.jib.api.buildplan.ModificationTimeProvider;\nimport com.google.cloud.tools.jib.api.buildplan.Platform;\nimport com.google.cloud.tools.jib.configuration.ImageConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration;\nimport com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.ImmutableSet;\nimport com.google.common.io.Resources;\nimport com.google.common.truth.Correspondence;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.function.Consumer;\nimport javax.annotation.Nullable;\nimport junitparams.JUnitParamsRunner;\nimport junitparams.Parameters;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Answers;\nimport org.mockito.ArgumentMatcher;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnit;\nimport org.mockito.junit.MockitoRule;\n\n/** Tests for {@link PluginConfigurationProcessor}. */\n@RunWith(JUnitParamsRunner.class)\npublic class PluginConfigurationProcessorTest {\n\n  private static class TestPlatformConfiguration implements PlatformConfiguration {\n    @Nullable private final String os;\n    @Nullable private final String architecture;\n\n    private TestPlatformConfiguration(@Nullable String architecture, @Nullable String os) {\n      this.architecture = architecture;\n      this.os = os;\n    }\n\n    @Override\n    public Optional<String> getOsName() {\n      return Optional.ofNullable(os);\n    }\n\n    @Override\n    public Optional<String> getArchitectureName() {\n      return Optional.ofNullable(architecture);\n    }\n  }\n\n  private static class TestExtraDirectoriesConfiguration implements ExtraDirectoriesConfiguration {\n\n    private final Path from;\n\n    private TestExtraDirectoriesConfiguration(Path from) {\n      this.from = from;\n    }\n\n    @Override\n    public Path getFrom() {\n      return from;\n    }\n\n    @Override\n    public String getInto() {\n      return \"/target/dir\";\n    }\n\n    @Override\n    public List<String> getIncludesList() {\n      return Collections.emptyList();\n    }\n\n    @Override\n    public List<String> getExcludesList() {\n      return Collections.emptyList();\n    }\n  }\n\n  private static final Correspondence<FileEntry, Path> SOURCE_FILE_OF =\n      Correspondence.transforming(FileEntry::getSourceFile, \"has sourceFile of\");\n  private static final Correspondence<FileEntry, String> EXTRACTION_PATH_OF =\n      Correspondence.transforming(\n          entry -> entry.getExtractionPath().toString(), \"has extractionPath of\");\n\n  private static List<FileEntry> getLayerEntries(ContainerBuildPlan buildPlan, String layerName) {\n    @SuppressWarnings(\"unchecked\")\n    List<FileEntriesLayer> layers = ((List<FileEntriesLayer>) buildPlan.getLayers());\n    return layers.stream()\n        .filter(layer -> layer.getName().equals(layerName))\n        .findFirst()\n        .get()\n        .getEntries();\n  }\n\n  @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule().silent();\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  @Mock(answer = Answers.RETURNS_SELF)\n  private Containerizer containerizer;\n\n  @Mock private RawConfiguration rawConfiguration;\n  @Mock private ProjectProperties projectProperties;\n  @Mock private InferredAuthProvider inferredAuthProvider;\n  @Mock private AuthProperty authProperty;\n  @Mock private Consumer<LogEvent> logger;\n  @Mock private CredHelperConfiguration fromCredHelper;\n  @Mock private CredHelperConfiguration toCredHelper;\n\n  private Path appCacheDirectory;\n  private final JibContainerBuilder jibContainerBuilder = Jib.fromScratch();\n\n  @Before\n  public void setUp() throws IOException, InvalidImageReferenceException, InferredAuthException {\n    when(rawConfiguration.getFromAuth()).thenReturn(authProperty);\n    when(rawConfiguration.getEntrypoint()).thenReturn(Optional.empty());\n    when(rawConfiguration.getAppRoot()).thenReturn(\"/app\");\n    Mockito.<List<?>>when(rawConfiguration.getPlatforms())\n        .thenReturn(Arrays.asList(new TestPlatformConfiguration(\"amd64\", \"linux\")));\n    when(rawConfiguration.getFilesModificationTime()).thenReturn(\"EPOCH_PLUS_SECOND\");\n    when(rawConfiguration.getCreationTime()).thenReturn(\"EPOCH\");\n    when(rawConfiguration.getContainerizingMode()).thenReturn(\"exploded\");\n    when(rawConfiguration.getFromCredHelper()).thenReturn(fromCredHelper);\n    when(rawConfiguration.getToCredHelper()).thenReturn(toCredHelper);\n    when(projectProperties.getMajorJavaVersion()).thenReturn(8);\n    when(projectProperties.getToolName()).thenReturn(\"tool\");\n    when(projectProperties.getToolVersion()).thenReturn(\"tool-version\");\n    when(projectProperties.getMainClassFromJarPlugin()).thenReturn(\"java.lang.Object\");\n    when(projectProperties.createJibContainerBuilder(\n            any(JavaContainerBuilder.class), any(ContainerizingMode.class)))\n        .thenReturn(Jib.from(\"base\"));\n    when(projectProperties.isOffline()).thenReturn(false);\n    when(projectProperties.getDependencies())\n        .thenReturn(Arrays.asList(Paths.get(\"/repo/foo-1.jar\"), Paths.get(\"/home/libs/bar-2.jar\")));\n\n    appCacheDirectory = temporaryFolder.newFolder(\"jib-cache\").toPath();\n    when(projectProperties.getDefaultCacheDirectory()).thenReturn(appCacheDirectory);\n\n    when(inferredAuthProvider.inferAuth(any())).thenReturn(Optional.empty());\n  }\n\n  @Test\n  public void testPluginConfigurationProcessor_defaults()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\n            \"java\", \"-cp\", \"/app/resources:/app/classes:/app/libs/*\", \"java.lang.Object\")\n        .inOrder();\n\n    verify(containerizer).setBaseImageLayersCache(Containerizer.DEFAULT_BASE_CACHE_DIRECTORY);\n    verify(containerizer).setApplicationLayersCache(appCacheDirectory);\n\n    ArgumentMatcher<LogEvent> isLogWarn = logEvent -> logEvent.getLevel() == LogEvent.Level.WARN;\n    verify(logger, never()).accept(argThat(isLogWarn));\n  }\n\n  @Test\n  public void testPluginConfigurationProcessor_extraDirectory()\n      throws URISyntaxException, InvalidContainerVolumeException, MainClassInferenceException,\n          InvalidAppRootException, IOException, IncompatibleBaseImageJavaVersionException,\n          InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidImageReferenceException, NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    Path extraDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI());\n    Mockito.<List<?>>when(rawConfiguration.getExtraDirectories())\n        .thenReturn(Arrays.asList(new TestExtraDirectoriesConfiguration(extraDirectory)));\n    when(rawConfiguration.getExtraDirectoryPermissions())\n        .thenReturn(ImmutableMap.of(\"/target/dir/foo\", FilePermissions.fromOctalString(\"123\")));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n    List<FileEntry> extraFiles = getLayerEntries(buildPlan, \"extra files\");\n\n    assertThat(extraFiles)\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            extraDirectory.resolve(\"a\"),\n            extraDirectory.resolve(\"a/b\"),\n            extraDirectory.resolve(\"a/b/bar\"),\n            extraDirectory.resolve(\"c\"),\n            extraDirectory.resolve(\"c/cat\"),\n            extraDirectory.resolve(\"foo\"));\n    assertThat(extraFiles)\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\n            \"/target/dir/a\",\n            \"/target/dir/a/b\",\n            \"/target/dir/a/b/bar\",\n            \"/target/dir/c\",\n            \"/target/dir/c/cat\",\n            \"/target/dir/foo\");\n\n    Optional<FileEntry> fooEntry =\n        extraFiles.stream()\n            .filter(\n                layerEntry ->\n                    layerEntry.getExtractionPath().equals(AbsoluteUnixPath.get(\"/target/dir/foo\")))\n            .findFirst();\n    assertThat(fooEntry).isPresent();\n    assertThat(fooEntry.get().getPermissions().toOctalString()).isEqualTo(\"123\");\n  }\n\n  @Test\n  public void testPluginConfigurationProcessor__errorOnExtraDirectoryPathNotFound()\n      throws URISyntaxException, NumberFormatException {\n    Path extraDirectory = Paths.get(Resources.getResource(\"core/layer\").toURI()).resolve(\"xyz\");\n    Mockito.<List<?>>when(rawConfiguration.getExtraDirectories())\n        .thenReturn(Arrays.asList(new TestExtraDirectoriesConfiguration(extraDirectory)));\n\n    Exception exception =\n        assertThrows(ExtraDirectoryNotFoundException.class, this::processCommonConfiguration);\n    assertThat(exception).hasMessageThat().isEqualTo(extraDirectory.toString());\n  }\n\n  @Test\n  public void testPluginConfigurationProcessor_cacheDirectorySystemProperties()\n      throws InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException,\n          IOException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidImageReferenceException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    System.setProperty(PropertyNames.BASE_IMAGE_CACHE, \"new/base/cache\");\n    System.setProperty(PropertyNames.APPLICATION_CACHE, \"/new/application/cache\");\n\n    processCommonConfiguration();\n\n    verify(containerizer).setBaseImageLayersCache(Paths.get(\"new/base/cache\"));\n    verify(containerizer).setApplicationLayersCache(Paths.get(\"/new/application/cache\"));\n  }\n\n  @Test\n  public void testAddJvmArgFilesLayer() throws IOException, InvalidAppRootException {\n    String classpath = \"/extra:/app/classes:/app/libs/dep.jar\";\n    String mainClass = \"com.example.Main\";\n    PluginConfigurationProcessor.addJvmArgFilesLayer(\n        rawConfiguration, projectProperties, jibContainerBuilder, classpath, mainClass);\n\n    Path classpathFile = appCacheDirectory.resolve(\"jib-classpath-file\");\n    Path mainClassFile = appCacheDirectory.resolve(\"jib-main-class-file\");\n    String classpathRead = new String(Files.readAllBytes(classpathFile), StandardCharsets.UTF_8);\n    String mainClassRead = new String(Files.readAllBytes(mainClassFile), StandardCharsets.UTF_8);\n    assertThat(classpathRead).isEqualTo(classpath);\n    assertThat(mainClassRead).isEqualTo(mainClass);\n\n    List<FileEntry> layerEntries =\n        getLayerEntries(jibContainerBuilder.toContainerBuildPlan(), \"jvm arg files\");\n    assertThat(layerEntries)\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            appCacheDirectory.resolve(\"jib-classpath-file\"),\n            appCacheDirectory.resolve(\"jib-main-class-file\"));\n    assertThat(layerEntries)\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/app/jib-classpath-file\", \"/app/jib-main-class-file\");\n  }\n\n  @Test\n  public void testWriteFileConservatively() throws IOException {\n    Path file = temporaryFolder.getRoot().toPath().resolve(\"file.txt\");\n\n    PluginConfigurationProcessor.writeFileConservatively(file, \"some content\");\n\n    String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);\n    assertThat(content).isEqualTo(\"some content\");\n  }\n\n  @Test\n  public void testWriteFileConservatively_updatedContent() throws IOException {\n    Path file = temporaryFolder.newFile().toPath();\n\n    PluginConfigurationProcessor.writeFileConservatively(file, \"some content\");\n\n    String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);\n    assertThat(content).isEqualTo(\"some content\");\n  }\n\n  @Test\n  public void testWriteFileConservatively_noWriteIfUnchanged() throws IOException {\n    Path file = temporaryFolder.newFile().toPath();\n    Files.write(file, \"some content\".getBytes(StandardCharsets.UTF_8));\n    FileTime fileTime = Files.getLastModifiedTime(file);\n\n    PluginConfigurationProcessor.writeFileConservatively(file, \"some content\");\n\n    String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);\n    assertThat(content).isEqualTo(\"some content\");\n    assertThat(Files.getLastModifiedTime(file)).isEqualTo(fileTime);\n  }\n\n  @Test\n  public void testEntrypoint()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getEntrypoint())\n        .thenReturn(Optional.of(Arrays.asList(\"custom\", \"entrypoint\")));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint()).containsExactly(\"custom\", \"entrypoint\").inOrder();\n    verifyNoInteractions(logger);\n  }\n\n  @Test\n  public void testComputeEntrypoint_inheritKeyword()\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    when(rawConfiguration.getEntrypoint())\n        .thenReturn(Optional.of(Collections.singletonList(\"INHERIT\")));\n\n    assertThat(\n            PluginConfigurationProcessor.computeEntrypoint(\n                rawConfiguration, projectProperties, jibContainerBuilder))\n        .isNull();\n  }\n\n  @Test\n  public void testComputeEntrypoint_inheritKeywordInNonSingletonList()\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    when(rawConfiguration.getEntrypoint()).thenReturn(Optional.of(Arrays.asList(\"INHERIT\", \"\")));\n\n    assertThat(\n            PluginConfigurationProcessor.computeEntrypoint(\n                rawConfiguration, projectProperties, jibContainerBuilder))\n        .isNotNull();\n  }\n\n  @Test\n  public void testComputeEntrypoint_default()\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    assertThat(\n            PluginConfigurationProcessor.computeEntrypoint(\n                rawConfiguration, projectProperties, jibContainerBuilder))\n        .containsExactly(\n            \"java\", \"-cp\", \"/app/resources:/app/classes:/app/libs/*\", \"java.lang.Object\")\n        .inOrder();\n  }\n\n  @Test\n  public void testComputeEntrypoint_packaged()\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    when(rawConfiguration.getContainerizingMode()).thenReturn(\"packaged\");\n    assertThat(\n            PluginConfigurationProcessor.computeEntrypoint(\n                rawConfiguration, projectProperties, jibContainerBuilder))\n        .containsExactly(\"java\", \"-cp\", \"/app/classpath/*:/app/libs/*\", \"java.lang.Object\")\n        .inOrder();\n  }\n\n  @Test\n  public void testComputeEntrypoint_expandClasspathDependencies()\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    when(rawConfiguration.getExpandClasspathDependencies()).thenReturn(true);\n    assertThat(\n            PluginConfigurationProcessor.computeEntrypoint(\n                rawConfiguration, projectProperties, jibContainerBuilder))\n        .containsExactly(\n            \"java\",\n            \"-cp\",\n            \"/app/resources:/app/classes:/app/libs/foo-1.jar:/app/libs/bar-2.jar\",\n            \"java.lang.Object\")\n        .inOrder();\n  }\n\n  @Test\n  public void testComputeEntrypoint_expandClasspathDependencies_sizeAddedForDuplicateJars()\n      throws MainClassInferenceException, InvalidAppRootException, IOException,\n          InvalidContainerizingModeException {\n    Path libFoo13 = temporaryFolder.newFolder().toPath().resolve(\"foo-1.jar\");\n    Path libFoo45 = temporaryFolder.newFolder().toPath().resolve(\"foo-1.jar\");\n    Files.write(libFoo13, new byte[13]);\n    Files.write(libFoo45, new byte[45]);\n\n    when(rawConfiguration.getExpandClasspathDependencies()).thenReturn(true);\n    when(projectProperties.getDependencies())\n        .thenReturn(Arrays.asList(libFoo13, Paths.get(\"/home/libs/bar-2.jar\"), libFoo45));\n\n    assertThat(\n            PluginConfigurationProcessor.computeEntrypoint(\n                rawConfiguration, projectProperties, jibContainerBuilder))\n        .containsExactly(\n            \"java\",\n            \"-cp\",\n            \"/app/resources:/app/classes:\"\n                + \"/app/libs/foo-1-13.jar:/app/libs/bar-2.jar:/app/libs/foo-1-45.jar\",\n            \"java.lang.Object\")\n        .inOrder();\n  }\n\n  @Test\n  public void testEntrypoint_defaultWarPackaging()\n      throws IOException, InvalidImageReferenceException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(projectProperties.isWarProject()).thenReturn(true);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\")\n        .inOrder();\n    verifyNoInteractions(logger);\n  }\n\n  @Test\n  public void testEntrypoint_defaultNonWarPackaging()\n      throws IOException, InvalidImageReferenceException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(projectProperties.isWarProject()).thenReturn(false);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\n            \"java\", \"-cp\", \"/app/resources:/app/classes:/app/libs/*\", \"java.lang.Object\")\n        .inOrder();\n\n    ArgumentMatcher<LogEvent> isLogWarn = logEvent -> logEvent.getLevel() == LogEvent.Level.WARN;\n    verify(logger, never()).accept(argThat(isLogWarn));\n  }\n\n  @Test\n  public void testEntrypoint_extraClasspathNonWarPackaging()\n      throws IOException, InvalidImageReferenceException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getExtraClasspath()).thenReturn(Collections.singletonList(\"/foo\"));\n    when(projectProperties.isWarProject()).thenReturn(false);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\n            \"java\", \"-cp\", \"/foo:/app/resources:/app/classes:/app/libs/*\", \"java.lang.Object\")\n        .inOrder();\n\n    ArgumentMatcher<LogEvent> isLogWarn = logEvent -> logEvent.getLevel() == LogEvent.Level.WARN;\n    verify(logger, never()).accept(argThat(isLogWarn));\n  }\n\n  @Test\n  public void testClasspathArgumentFile()\n      throws NumberFormatException, InvalidImageReferenceException, MainClassInferenceException,\n          InvalidAppRootException, IOException, InvalidWorkingDirectoryException,\n          InvalidPlatformException, InvalidContainerVolumeException,\n          IncompatibleBaseImageJavaVersionException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getExtraClasspath()).thenReturn(Collections.singletonList(\"/foo\"));\n    when(projectProperties.getMajorJavaVersion()).thenReturn(9);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-cp\", \"@/app/jib-classpath-file\", \"java.lang.Object\")\n        .inOrder();\n\n    List<FileEntry> jvmArgFiles = getLayerEntries(buildPlan, \"jvm arg files\");\n    assertThat(jvmArgFiles)\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            appCacheDirectory.resolve(\"jib-classpath-file\"),\n            appCacheDirectory.resolve(\"jib-main-class-file\"));\n    assertThat(jvmArgFiles)\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/app/jib-classpath-file\", \"/app/jib-main-class-file\");\n\n    String classpath =\n        new String(\n            Files.readAllBytes(appCacheDirectory.resolve(\"jib-classpath-file\")),\n            StandardCharsets.UTF_8);\n    assertThat(classpath)\n        .isEqualTo(\"/foo:/app/resources:/app/classes:/app/libs/foo-1.jar:/app/libs/bar-2.jar\");\n    String mainClass =\n        new String(\n            Files.readAllBytes(appCacheDirectory.resolve(\"jib-main-class-file\")),\n            StandardCharsets.UTF_8);\n    assertThat(mainClass).isEqualTo(\"java.lang.Object\");\n  }\n\n  @Test\n  public void testClasspathArgumentFile_mainClassInferenceFailureWithCustomEntrypoint()\n      throws NumberFormatException, InvalidImageReferenceException, MainClassInferenceException,\n          InvalidAppRootException, IOException, InvalidWorkingDirectoryException,\n          InvalidPlatformException, InvalidContainerVolumeException,\n          IncompatibleBaseImageJavaVersionException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getMainClass()).thenReturn(Optional.of(\"invalid main class\"));\n    when(rawConfiguration.getEntrypoint()).thenReturn(Optional.of(Arrays.asList(\"bash\")));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint()).containsExactly(\"bash\");\n\n    List<FileEntry> jvmArgFiles = getLayerEntries(buildPlan, \"jvm arg files\");\n    assertThat(jvmArgFiles)\n        .comparingElementsUsing(SOURCE_FILE_OF)\n        .containsExactly(\n            appCacheDirectory.resolve(\"jib-classpath-file\"),\n            appCacheDirectory.resolve(\"jib-main-class-file\"));\n    assertThat(jvmArgFiles)\n        .comparingElementsUsing(EXTRACTION_PATH_OF)\n        .containsExactly(\"/app/jib-classpath-file\", \"/app/jib-main-class-file\");\n\n    String classpath =\n        new String(\n            Files.readAllBytes(appCacheDirectory.resolve(\"jib-classpath-file\")),\n            StandardCharsets.UTF_8);\n    assertThat(classpath).isEqualTo(\"/app/resources:/app/classes:/app/libs/*\");\n    String mainClass =\n        new String(\n            Files.readAllBytes(appCacheDirectory.resolve(\"jib-main-class-file\")),\n            StandardCharsets.UTF_8);\n    assertThat(mainClass).isEqualTo(\"could-not-infer-a-main-class\");\n  }\n\n  @Test\n  public void testUser()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getUser()).thenReturn(Optional.of(\"customUser\"));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n    assertThat(buildPlan.getUser()).isEqualTo(\"customUser\");\n  }\n\n  @Test\n  public void testUser_null()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n    assertThat(buildPlan.getUser()).isNull();\n  }\n\n  @Test\n  public void testEntrypoint_warningOnJvmFlags()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getEntrypoint())\n        .thenReturn(Optional.of(Arrays.asList(\"custom\", \"entrypoint\")));\n    when(rawConfiguration.getJvmFlags()).thenReturn(Collections.singletonList(\"jvmFlag\"));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint()).containsExactly(\"custom\", \"entrypoint\").inOrder();\n    verify(projectProperties)\n        .log(\n            LogEvent.info(\n                \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored \"\n                    + \"when entrypoint is specified\"));\n  }\n\n  @Test\n  public void testEntrypoint_warningOnMainclass()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getEntrypoint())\n        .thenReturn(Optional.of(Arrays.asList(\"custom\", \"entrypoint\")));\n    when(rawConfiguration.getMainClass()).thenReturn(Optional.of(\"java.util.Object\"));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint()).containsExactly(\"custom\", \"entrypoint\").inOrder();\n    verify(projectProperties)\n        .log(\n            LogEvent.info(\n                \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored \"\n                    + \"when entrypoint is specified\"));\n  }\n\n  @Test\n  public void testEntrypoint_warningOnExpandClasspathDependencies()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getEntrypoint())\n        .thenReturn(Optional.of(Arrays.asList(\"custom\", \"entrypoint\")));\n    when(rawConfiguration.getExpandClasspathDependencies()).thenReturn(true);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint()).containsExactly(\"custom\", \"entrypoint\").inOrder();\n    verify(projectProperties)\n        .log(\n            LogEvent.info(\n                \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored \"\n                    + \"when entrypoint is specified\"));\n  }\n\n  @Test\n  public void testEntrypoint_warningOnMainclassForWar()\n      throws IOException, InvalidCreationTimeException, InvalidImageReferenceException,\n          IncompatibleBaseImageJavaVersionException, InvalidPlatformException,\n          InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException,\n          InvalidWorkingDirectoryException, InvalidFilesModificationTimeException,\n          InvalidContainerizingModeException, ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getMainClass()).thenReturn(Optional.of(\"java.util.Object\"));\n    when(projectProperties.isWarProject()).thenReturn(true);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\")\n        .inOrder();\n    verify(projectProperties)\n        .log(\n            LogEvent.warn(\n                \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies \"\n                    + \"are ignored for WAR projects\"));\n  }\n\n  @Test\n  public void testEntrypoint_warningOnExpandClasspathDependenciesForWar()\n      throws IOException, InvalidCreationTimeException, InvalidImageReferenceException,\n          IncompatibleBaseImageJavaVersionException, InvalidPlatformException,\n          InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException,\n          InvalidWorkingDirectoryException, InvalidFilesModificationTimeException,\n          InvalidContainerizingModeException, ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getExpandClasspathDependencies()).thenReturn(true);\n    when(projectProperties.isWarProject()).thenReturn(true);\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\"java\", \"-jar\", \"/usr/local/jetty/start.jar\", \"--module=ee10-deploy\")\n        .inOrder();\n    verify(projectProperties)\n        .log(\n            LogEvent.warn(\n                \"mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies \"\n                    + \"are ignored for WAR projects\"));\n  }\n\n  @Test\n  public void testEntrypointClasspath_nonDefaultAppRoot()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"/my/app\");\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint())\n        .containsExactly(\n            \"java\", \"-cp\", \"/my/app/resources:/my/app/classes:/my/app/libs/*\", \"java.lang.Object\")\n        .inOrder();\n  }\n\n  @Test\n  public void testWebAppEntrypoint_inheritedFromCustomBaseImage()\n      throws InvalidImageReferenceException, IOException, MainClassInferenceException,\n          InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    when(projectProperties.isWarProject()).thenReturn(true);\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(\"custom-base-image\"));\n\n    ContainerBuildPlan buildPlan = processCommonConfiguration();\n\n    assertThat(buildPlan.getEntrypoint()).isNull();\n  }\n\n  @Test\n  public void testGetAppRootChecked() throws InvalidAppRootException {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"/some/root\");\n\n    assertThat(PluginConfigurationProcessor.getAppRootChecked(rawConfiguration, projectProperties))\n        .isEqualTo(AbsoluteUnixPath.get(\"/some/root\"));\n  }\n\n  @Test\n  public void testGetAppRootChecked_errorOnNonAbsolutePath() {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"relative/path\");\n\n    Exception exception =\n        assertThrows(\n            InvalidAppRootException.class,\n            () ->\n                PluginConfigurationProcessor.getAppRootChecked(\n                    rawConfiguration, projectProperties));\n    assertThat(exception).hasMessageThat().isEqualTo(\"relative/path\");\n  }\n\n  @Test\n  public void testGetAppRootChecked_errorOnWindowsPath() {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"\\\\windows\\\\path\");\n\n    Exception exception =\n        assertThrows(\n            InvalidAppRootException.class,\n            () ->\n                PluginConfigurationProcessor.getAppRootChecked(\n                    rawConfiguration, projectProperties));\n    assertThat(exception).hasMessageThat().isEqualTo(\"\\\\windows\\\\path\");\n  }\n\n  @Test\n  public void testGetAppRootChecked_errorOnWindowsPathWithDriveLetter() {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"C:\\\\windows\\\\path\");\n\n    Exception exception =\n        assertThrows(\n            InvalidAppRootException.class,\n            () ->\n                PluginConfigurationProcessor.getAppRootChecked(\n                    rawConfiguration, projectProperties));\n    assertThat(exception).hasMessageThat().isEqualTo(\"C:\\\\windows\\\\path\");\n  }\n\n  @Test\n  public void testGetAppRootChecked_defaultNonWarProject() throws InvalidAppRootException {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"\");\n    when(projectProperties.isWarProject()).thenReturn(false);\n\n    assertThat(PluginConfigurationProcessor.getAppRootChecked(rawConfiguration, projectProperties))\n        .isEqualTo(AbsoluteUnixPath.get(\"/app\"));\n  }\n\n  @Test\n  public void testGetAppRootChecked_defaultWarProject() throws InvalidAppRootException {\n    when(rawConfiguration.getAppRoot()).thenReturn(\"\");\n    when(projectProperties.isWarProject()).thenReturn(true);\n\n    assertThat(PluginConfigurationProcessor.getAppRootChecked(rawConfiguration, projectProperties))\n        .isEqualTo(AbsoluteUnixPath.get(\"/var/lib/jetty/webapps/ROOT\"));\n  }\n\n  @Test\n  public void testGetContainerizingModeChecked_packagedWithWar() {\n    when(rawConfiguration.getContainerizingMode()).thenReturn(\"packaged\");\n    when(projectProperties.isWarProject()).thenReturn(true);\n\n    Exception exception =\n        assertThrows(\n            UnsupportedOperationException.class,\n            () ->\n                PluginConfigurationProcessor.getContainerizingModeChecked(\n                    rawConfiguration, projectProperties));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"packaged containerizing mode for WAR is not yet supported\");\n  }\n\n  @Test\n  public void testGetWorkingDirectoryChecked() throws InvalidWorkingDirectoryException {\n    when(rawConfiguration.getWorkingDirectory()).thenReturn(Optional.of(\"/valid/path\"));\n\n    Optional<AbsoluteUnixPath> checkedPath =\n        PluginConfigurationProcessor.getWorkingDirectoryChecked(rawConfiguration);\n    assertThat(checkedPath).hasValue(AbsoluteUnixPath.get(\"/valid/path\"));\n  }\n\n  @Test\n  public void testGetWorkingDirectoryChecked_undefined() throws InvalidWorkingDirectoryException {\n    when(rawConfiguration.getWorkingDirectory()).thenReturn(Optional.empty());\n    assertThat(PluginConfigurationProcessor.getWorkingDirectoryChecked(rawConfiguration)).isEmpty();\n  }\n\n  @Test\n  public void testGetWorkingDirectoryChecked_notAbsolute() {\n    when(rawConfiguration.getWorkingDirectory()).thenReturn(Optional.of(\"relative/path\"));\n\n    InvalidWorkingDirectoryException exception =\n        assertThrows(\n            InvalidWorkingDirectoryException.class,\n            () -> PluginConfigurationProcessor.getWorkingDirectoryChecked(rawConfiguration));\n    assertThat(exception).hasMessageThat().isEqualTo(\"relative/path\");\n    assertThat(exception.getInvalidPathValue()).isEqualTo(\"relative/path\");\n  }\n\n  @Test\n  public void testGetDefaultBaseImage_nonWarPackaging()\n      throws IncompatibleBaseImageJavaVersionException {\n    when(projectProperties.isWarProject()).thenReturn(false);\n\n    assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties))\n        .isEqualTo(\"eclipse-temurin:8-jre\");\n  }\n\n  @Test\n  public void testGetDefaultBaseImage_warProject()\n      throws IncompatibleBaseImageJavaVersionException {\n    when(projectProperties.isWarProject()).thenReturn(true);\n\n    assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties))\n        .isEqualTo(\"jetty\");\n  }\n\n  @Test\n  @Parameters(\n      value = {\n        \"6, eclipse-temurin:8-jre\",\n        \"8, eclipse-temurin:8-jre\",\n        \"9, eclipse-temurin:11-jre\",\n        \"11, eclipse-temurin:11-jre\",\n        \"13, eclipse-temurin:17-jre\",\n        \"17, eclipse-temurin:17-jre\",\n        \"21, eclipse-temurin:21-jre\",\n        \"25, eclipse-temurin:25-jre\"\n      })\n  public void testGetDefaultBaseImage_defaultJavaBaseImage(\n      int javaVersion, String expectedBaseImage) throws IncompatibleBaseImageJavaVersionException {\n    when(projectProperties.getMajorJavaVersion()).thenReturn(javaVersion);\n    assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties))\n        .isEqualTo(expectedBaseImage);\n  }\n\n  @Test\n  public void testGetDefaultBaseImage_projectHigherThanJava25() {\n    when(projectProperties.getMajorJavaVersion()).thenReturn(26);\n\n    IncompatibleBaseImageJavaVersionException exception =\n        assertThrows(\n            IncompatibleBaseImageJavaVersionException.class,\n            () -> PluginConfigurationProcessor.getDefaultBaseImage(projectProperties));\n\n    assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(25);\n    assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(26);\n  }\n\n  @Test\n  public void testGetJavaContainerBuilderWithBaseImage_dockerBase()\n      throws IncompatibleBaseImageJavaVersionException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(\"docker://ima.ge/name\"));\n    ImageConfiguration result = getCommonImageConfiguration();\n    assertThat(result.getImage().toString()).isEqualTo(\"ima.ge/name\");\n    assertThat(result.getDockerClient()).isPresent();\n    assertThat(result.getTarPath()).isEmpty();\n  }\n\n  @Test\n  public void testGetJavaContainerBuilderWithBaseImage_tarBase()\n      throws IncompatibleBaseImageJavaVersionException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(\"tar:///path/to.tar\"));\n    ImageConfiguration result = getCommonImageConfiguration();\n    assertThat(result.getTarPath()).hasValue(Paths.get(\"/path/to.tar\"));\n    assertThat(result.getDockerClient()).isEmpty();\n  }\n\n  @Test\n  public void testGetJavaContainerBuilderWithBaseImage_registry()\n      throws IncompatibleBaseImageJavaVersionException, InvalidImageReferenceException, IOException,\n          CacheDirectoryCreationException {\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(\"ima.ge/name\"));\n    ImageConfiguration result = getCommonImageConfiguration();\n    assertThat(result.getImage().toString()).isEqualTo(\"ima.ge/name\");\n    assertThat(result.getDockerClient()).isEmpty();\n    assertThat(result.getTarPath()).isEmpty();\n  }\n\n  @Test\n  public void testGetJavaContainerBuilderWithBaseImage_registryWithPrefix()\n      throws IncompatibleBaseImageJavaVersionException, InvalidImageReferenceException, IOException,\n          CacheDirectoryCreationException {\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(\"registry://ima.ge/name\"));\n    ImageConfiguration result = getCommonImageConfiguration();\n    assertThat(result.getImage().toString()).isEqualTo(\"ima.ge/name\");\n    assertThat(result.getDockerClient()).isEmpty();\n    assertThat(result.getTarPath()).isEmpty();\n  }\n\n  @Test\n  @Parameters(\n      value = {\n        \"adoptopenjdk:8, 8, 11\",\n        \"adoptopenjdk:8-jre, 8, 11\",\n        \"eclipse-temurin:8, 8, 11\",\n        \"eclipse-temurin:8-jre, 8, 11\",\n        \"adoptopenjdk:11, 11, 15\",\n        \"adoptopenjdk:11-jre, 11, 15\",\n        \"eclipse-temurin:11, 11, 15\",\n        \"eclipse-temurin:11-jre, 11, 15\",\n        \"eclipse-temurin:17, 17, 19\",\n        \"eclipse-temurin:17-jre, 17, 19\",\n        \"eclipse-temurin:21, 21, 22\",\n        \"eclipse-temurin:21-jre, 21, 22\",\n        \"eclipse-temurin:25, 25, 26\",\n        \"eclipse-temurin:25-jre, 25, 26\"\n      })\n  public void testGetJavaContainerBuilderWithBaseImage_incompatibleJavaBaseImage(\n      String baseImage, int baseImageJavaVersion, int appJavaVersion) {\n    when(projectProperties.getMajorJavaVersion()).thenReturn(appJavaVersion);\n\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(baseImage));\n    IncompatibleBaseImageJavaVersionException exception =\n        assertThrows(\n            IncompatibleBaseImageJavaVersionException.class,\n            () ->\n                PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage(\n                    rawConfiguration, projectProperties, inferredAuthProvider));\n    assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(baseImageJavaVersion);\n    assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(appJavaVersion);\n  }\n\n  // https://github.com/GoogleContainerTools/jib/issues/1995\n  @Test\n  public void testGetJavaContainerBuilderWithBaseImage_java12BaseImage()\n      throws InvalidImageReferenceException, IOException, IncompatibleBaseImageJavaVersionException,\n          CacheDirectoryCreationException {\n    when(projectProperties.getMajorJavaVersion()).thenReturn(12);\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.of(\"regis.try/java12image\"));\n    ImageConfiguration imageConfiguration = getCommonImageConfiguration();\n    assertThat(imageConfiguration.getImageRegistry()).isEqualTo(\"regis.try\");\n    assertThat(imageConfiguration.getImageRepository()).isEqualTo(\"java12image\");\n  }\n\n  @Test\n  public void testGetJavaContainerBuilderWithBaseImage_java26NoBaseImage() {\n    when(projectProperties.getMajorJavaVersion()).thenReturn(26);\n    when(rawConfiguration.getFromImage()).thenReturn(Optional.empty());\n    IncompatibleBaseImageJavaVersionException exception =\n        assertThrows(\n            IncompatibleBaseImageJavaVersionException.class,\n            () ->\n                PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage(\n                    rawConfiguration, projectProperties, inferredAuthProvider));\n    assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(25);\n    assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(26);\n  }\n\n  @Test\n  public void testGetPlatformsSet() throws InvalidPlatformException {\n    Mockito.<List<?>>when(rawConfiguration.getPlatforms())\n        .thenReturn(Arrays.asList(new TestPlatformConfiguration(\"testArchitecture\", \"testOs\")));\n\n    assertThat(PluginConfigurationProcessor.getPlatformsSet(rawConfiguration))\n        .containsExactly(new Platform(\"testArchitecture\", \"testOs\"));\n  }\n\n  @Test\n  public void testGetPlatformsSet_architectureMissing() {\n    TestPlatformConfiguration platform = new TestPlatformConfiguration(null, \"testOs\");\n    Mockito.<List<?>>when(rawConfiguration.getPlatforms()).thenReturn(Arrays.asList(platform));\n\n    InvalidPlatformException exception =\n        assertThrows(\n            InvalidPlatformException.class,\n            () -> PluginConfigurationProcessor.getPlatformsSet(rawConfiguration));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"platform configuration is missing an architecture value\");\n    assertThat(exception.getInvalidPlatform()).isEqualTo(\"architecture=<missing>, os=testOs\");\n  }\n\n  @Test\n  public void testGetPlatformsSet_osMissing() {\n    TestPlatformConfiguration platform = new TestPlatformConfiguration(\"testArchitecture\", null);\n    Mockito.<List<?>>when(rawConfiguration.getPlatforms()).thenReturn(Arrays.asList(platform));\n\n    InvalidPlatformException exception =\n        assertThrows(\n            InvalidPlatformException.class,\n            () -> PluginConfigurationProcessor.getPlatformsSet(rawConfiguration));\n    assertThat(exception)\n        .hasMessageThat()\n        .isEqualTo(\"platform configuration is missing an OS value\");\n    assertThat(exception.getInvalidPlatform())\n        .isEqualTo(\"architecture=testArchitecture, os=<missing>\");\n  }\n\n  @Test\n  public void testGetValidVolumesList() throws InvalidContainerVolumeException {\n    when(rawConfiguration.getVolumes()).thenReturn(Collections.singletonList(\"/some/root\"));\n\n    assertThat(PluginConfigurationProcessor.getVolumesSet(rawConfiguration))\n        .containsExactly(AbsoluteUnixPath.get(\"/some/root\"));\n  }\n\n  @Test\n  public void testGetInvalidVolumesList() {\n    when(rawConfiguration.getVolumes()).thenReturn(Collections.singletonList(\"`some/root\"));\n\n    InvalidContainerVolumeException exception =\n        assertThrows(\n            InvalidContainerVolumeException.class,\n            () -> PluginConfigurationProcessor.getVolumesSet(rawConfiguration));\n    assertThat(exception).hasMessageThat().isEqualTo(\"`some/root\");\n    assertThat(exception.getInvalidVolume()).isEqualTo(\"`some/root\");\n  }\n\n  @Test\n  public void testCreateModificationTimeProvider_epochPlusSecond()\n      throws InvalidFilesModificationTimeException {\n    ModificationTimeProvider timeProvider =\n        PluginConfigurationProcessor.createModificationTimeProvider(\"EPOCH_PLUS_SECOND\");\n    assertThat(timeProvider.get(Paths.get(\"foo\"), AbsoluteUnixPath.get(\"/bar\")))\n        .isEqualTo(Instant.ofEpochSecond(1));\n  }\n\n  @Test\n  public void testCreateModificationTimeProvider_isoDateTimeValue()\n      throws InvalidFilesModificationTimeException {\n    ModificationTimeProvider timeProvider =\n        PluginConfigurationProcessor.createModificationTimeProvider(\"2011-12-03T10:15:30+09:00\");\n    Instant expected = DateTimeFormatter.ISO_DATE_TIME.parse(\"2011-12-03T01:15:30Z\", Instant::from);\n    assertThat(timeProvider.get(Paths.get(\"foo\"), AbsoluteUnixPath.get(\"/bar\")))\n        .isEqualTo(expected);\n  }\n\n  @Test\n  public void testCreateModificationTimeProvider_invalidValue() {\n    InvalidFilesModificationTimeException exception =\n        assertThrows(\n            InvalidFilesModificationTimeException.class,\n            () -> PluginConfigurationProcessor.createModificationTimeProvider(\"invalid format\"));\n    assertThat(exception).hasMessageThat().isEqualTo(\"invalid format\");\n    assertThat(exception.getInvalidFilesModificationTime()).isEqualTo(\"invalid format\");\n  }\n\n  @Test\n  public void testGetCreationTime_epoch() throws InvalidCreationTimeException {\n    Instant time = PluginConfigurationProcessor.getCreationTime(\"EPOCH\", projectProperties);\n    assertThat(time).isEqualTo(Instant.EPOCH);\n  }\n\n  @Test\n  public void testGetCreationTime_useCurrentTimestamp() throws InvalidCreationTimeException {\n    Instant now = Instant.now().minusSeconds(2);\n    Instant time =\n        PluginConfigurationProcessor.getCreationTime(\"USE_CURRENT_TIMESTAMP\", projectProperties);\n    assertThat(time).isGreaterThan(now);\n  }\n\n  @Test\n  public void testGetCreationTime_isoDateTimeValue() throws InvalidCreationTimeException {\n    Instant expected = DateTimeFormatter.ISO_DATE_TIME.parse(\"2011-12-03T01:15:30Z\", Instant::from);\n    List<String> validTimeStamps =\n        ImmutableList.of(\n            \"2011-12-03T10:15:30+09:00\",\n            \"2011-12-03T10:15:30+09:00[Asia/Tokyo]\",\n            \"2011-12-02T16:15:30-09:00\",\n            \"2011-12-03T10:15:30+0900\",\n            \"2011-12-02T16:15:30-0900\",\n            \"2011-12-03T10:15:30+09\",\n            \"2011-12-02T16:15:30-09\",\n            \"2011-12-03T01:15:30Z\");\n    for (String timeString : validTimeStamps) {\n      Instant time = PluginConfigurationProcessor.getCreationTime(timeString, projectProperties);\n      assertThat(time).isEqualTo(expected);\n    }\n  }\n\n  @Test\n  public void testGetCreationTime_isoDateTimeValueTimeZoneRegionOnlyAllowedForMostStrict8601Mode() {\n    List<String> invalidTimeStamps =\n        ImmutableList.of(\n            \"2011-12-03T01:15:30+0900[Asia/Tokyo]\", \"2011-12-03T01:15:30+09[Asia/Tokyo]\");\n    for (String timeString : invalidTimeStamps) {\n      // getCreationTime should fail if region specified when zone not in HH:MM mode.\n      // This is the expected behavior, not specifically designed like this for any reason, feel\n      // free to change this behavior and update the test\n      assertThrows(\n          InvalidCreationTimeException.class,\n          () -> PluginConfigurationProcessor.getCreationTime(timeString, projectProperties));\n    }\n  }\n\n  @Test\n  public void testGetCreationTime_isoDateTimeValueRequiresTimeZone() {\n    // getCreationTime should fail if timezone not specified.\n    // this is the expected behavior, not specifically designed like this for any reason, feel\n    // free to change this behavior and update the test\n    assertThrows(\n        InvalidCreationTimeException.class,\n        () ->\n            PluginConfigurationProcessor.getCreationTime(\"2011-12-03T01:15:30\", projectProperties));\n  }\n\n  @Test\n  public void testGetCreationTime_invalidValue() {\n    InvalidCreationTimeException exception =\n        assertThrows(\n            InvalidCreationTimeException.class,\n            () ->\n                PluginConfigurationProcessor.getCreationTime(\"invalid format\", projectProperties));\n    assertThat(exception).hasMessageThat().isEqualTo(\"invalid format\");\n    assertThat(exception.getInvalidCreationTime()).isEqualTo(\"invalid format\");\n  }\n\n  private ImageConfiguration getCommonImageConfiguration()\n      throws IncompatibleBaseImageJavaVersionException, IOException, InvalidImageReferenceException,\n          CacheDirectoryCreationException {\n    JibContainerBuilder containerBuilder =\n        PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage(\n                rawConfiguration, projectProperties, inferredAuthProvider)\n            .addClasses(temporaryFolder.getRoot().toPath())\n            .toContainerBuilder();\n    return JibContainerBuilderTestHelper.toBuildContext(\n            containerBuilder, Containerizer.to(RegistryImage.named(\"ignored\")))\n        .getBaseImageConfiguration();\n  }\n\n  private ContainerBuildPlan processCommonConfiguration()\n      throws InvalidImageReferenceException, MainClassInferenceException, InvalidAppRootException,\n          IOException, InvalidWorkingDirectoryException, InvalidPlatformException,\n          InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException,\n          NumberFormatException, InvalidContainerizingModeException,\n          InvalidFilesModificationTimeException, InvalidCreationTimeException,\n          ExtraDirectoryNotFoundException {\n    JibContainerBuilder containerBuilder =\n        PluginConfigurationProcessor.processCommonConfiguration(\n            rawConfiguration, ignored -> Optional.empty(), projectProperties, containerizer);\n    return containerBuilder.toContainerBuildPlan();\n  }\n\n  @Test\n  public void getAllFiles_expandsDirectories() throws IOException {\n    File rootFile = temporaryFolder.newFile(\"file\");\n    File folder = temporaryFolder.newFolder(\"folder\");\n    File folderFile = temporaryFolder.newFile(\"folder/file2\");\n    assertThat(\n            PluginConfigurationProcessor.getAllFiles(\n                ImmutableSet.of(rootFile.toPath(), folder.toPath())))\n        .containsExactly(rootFile.toPath().toAbsolutePath(), folderFile.toPath().toAbsolutePath());\n  }\n\n  @Test\n  public void getAllFiles_doesntBreakForNonExistentFiles() throws IOException {\n    Path testPath = Paths.get(\"/a/file/that/doesnt/exist\");\n    assertThat(Files.exists(testPath)).isFalse();\n    assertThat(PluginConfigurationProcessor.getAllFiles(ImmutableSet.of(testPath))).isEmpty();\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/SkaffoldFilesOutputTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.common.collect.ImmutableList;\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link SkaffoldFilesOutput}. */\npublic class SkaffoldFilesOutputTest {\n\n  private static final String TEST_JSON =\n      \"{\\\"build\\\":[\\\"buildFile1\\\",\\\"buildFile2\\\"],\\\"inputs\\\":[\\\"input1\\\",\\\"input2\\\"],\\\"ignore\\\":[\\\"ignore1\\\",\\\"ignore2\\\"]}\";\n\n  @Test\n  public void testGetJsonString() throws IOException {\n    SkaffoldFilesOutput skaffoldFilesOutput = new SkaffoldFilesOutput();\n    skaffoldFilesOutput.addBuild(Paths.get(\"buildFile1\"));\n    skaffoldFilesOutput.addBuild(Paths.get(\"buildFile2\"));\n    skaffoldFilesOutput.addInput(Paths.get(\"input1\"));\n    skaffoldFilesOutput.addInput(Paths.get(\"input2\"));\n    skaffoldFilesOutput.addIgnore(Paths.get(\"ignore1\"));\n    skaffoldFilesOutput.addIgnore(Paths.get(\"ignore2\"));\n    Assert.assertEquals(TEST_JSON, skaffoldFilesOutput.getJsonString());\n  }\n\n  @Test\n  public void testGetJsonString_empty() throws IOException {\n    SkaffoldFilesOutput skaffoldFilesOutput = new SkaffoldFilesOutput();\n    Assert.assertEquals(\n        \"{\\\"build\\\":[],\\\"inputs\\\":[],\\\"ignore\\\":[]}\", skaffoldFilesOutput.getJsonString());\n  }\n\n  @Test\n  public void testConstructor_json() throws IOException {\n    SkaffoldFilesOutput skaffoldFilesOutput = new SkaffoldFilesOutput(TEST_JSON);\n    Assert.assertEquals(\n        ImmutableList.of(\"buildFile1\", \"buildFile2\"), skaffoldFilesOutput.getBuild());\n    Assert.assertEquals(ImmutableList.of(\"input1\", \"input2\"), skaffoldFilesOutput.getInputs());\n    Assert.assertEquals(ImmutableList.of(\"ignore1\", \"ignore2\"), skaffoldFilesOutput.getIgnore());\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/SkaffoldSyncMapTemplateTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.fasterxml.jackson.databind.exc.MismatchedInputException;\nimport com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;\nimport com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;\nimport com.google.cloud.tools.jib.api.buildplan.FileEntry;\nimport com.google.cloud.tools.jib.api.buildplan.FilePermissions;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link SkaffoldFilesOutput}. */\npublic class SkaffoldSyncMapTemplateTest {\n\n  private static final Path GEN_SRC = Paths.get(\"/gen/src/\").toAbsolutePath();\n  private static final Path DIR_SRC_1 = Paths.get(\"/dir/src/\").toAbsolutePath();\n  private static final Path DIR_SRC_2 = Paths.get(\"/dir/src/\").toAbsolutePath();\n\n  private static final String TEST_JSON =\n      \"{\\\"generated\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(GEN_SRC)\n          + \"\\\",\\\"dest\\\":\\\"/genDest\\\"}],\\\"direct\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_1)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest1\\\"},{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_2)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest2\\\"}]}\";\n  private static final String TEST_JSON_EMPTY_GENERATED =\n      \"{\\\"generated\\\":[],\\\"direct\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_1)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest1\\\"},{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_2)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest2\\\"}]}\";\n  private static final String TEST_JSON_NO_GENERATED =\n      \"{\\\"direct\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_1)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest1\\\"},{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_2)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest2\\\"}]}\";\n  private static final String FAIL_TEST_JSON_MISSING_FIELD =\n      \"{\\\"generated\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(GEN_SRC)\n          + \"\\\"}],\\\"direct\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_1)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest1\\\"},{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_2)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest2\\\"}]}\";\n  private static final String FAIL_TEST_JSON_BAD_PROPERTY_NAME =\n      \"{\\\"generated\\\":[{\\\"jean-luc\\\":\\\"picard\\\", \\\"src\\\":\\\"\"\n          + getPathForJson(GEN_SRC)\n          + \"\\\",\\\"dest\\\":\\\"/genDest\\\"}],\\\"direct\\\":[{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_1)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest1\\\"},{\\\"src\\\":\\\"\"\n          + getPathForJson(DIR_SRC_2)\n          + \"\\\",\\\"dest\\\":\\\"/dirDest2\\\"}]}\";\n\n  // manually correct \"\\\" that we inject into the strings above for windows paths, this is only\n  // needed for this test, when json writes the string out in the actual code, it does the right\n  // thing\n  private static String getPathForJson(Path path) {\n    return path.toString().replace(\"\\\\\", \"\\\\\\\\\");\n  }\n\n  @Test\n  public void testFrom_badPropertyName() throws IOException {\n    try {\n      SkaffoldSyncMapTemplate.from(FAIL_TEST_JSON_BAD_PROPERTY_NAME);\n      Assert.fail();\n    } catch (UnrecognizedPropertyException ex) {\n      Assert.assertTrue(ex.getMessage().contains(\"Unrecognized field \\\"jean-luc\\\"\"));\n    }\n  }\n\n  @Test\n  public void testFrom_missingField() throws IOException {\n    try {\n      SkaffoldSyncMapTemplate.from(FAIL_TEST_JSON_MISSING_FIELD);\n      Assert.fail();\n    } catch (MismatchedInputException ex) {\n      Assert.assertTrue(ex.getMessage().contains(\"Missing required creator property 'dest'\"));\n    }\n  }\n\n  @Test\n  public void testFrom_validEmpty() throws Exception {\n    SkaffoldSyncMapTemplate templateEmptyGenerated =\n        SkaffoldSyncMapTemplate.from(TEST_JSON_EMPTY_GENERATED);\n    SkaffoldSyncMapTemplate templateNoGenerated =\n        SkaffoldSyncMapTemplate.from(TEST_JSON_NO_GENERATED);\n    Assert.assertTrue(templateEmptyGenerated.getGenerated().isEmpty());\n    Assert.assertTrue(templateNoGenerated.getGenerated().isEmpty());\n  }\n\n  @Test\n  public void testGetJsonString() throws IOException {\n    SkaffoldSyncMapTemplate ssmt = new SkaffoldSyncMapTemplate();\n    ssmt.addGenerated(\n        new FileEntry(\n            GEN_SRC,\n            AbsoluteUnixPath.get(\"/genDest\"),\n            FilePermissions.DEFAULT_FILE_PERMISSIONS,\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME));\n    ssmt.addDirect(\n        new FileEntry(\n            DIR_SRC_1,\n            AbsoluteUnixPath.get(\"/dirDest1\"),\n            FilePermissions.DEFAULT_FILE_PERMISSIONS,\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME));\n    ssmt.addDirect(\n        new FileEntry(\n            DIR_SRC_2,\n            AbsoluteUnixPath.get(\"/dirDest2\"),\n            FilePermissions.DEFAULT_FILE_PERMISSIONS,\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME));\n    Assert.assertEquals(TEST_JSON, ssmt.getJsonString());\n  }\n\n  @Test\n  public void testGetJsonString_emptyGenerated() throws IOException {\n    SkaffoldSyncMapTemplate ssmt = new SkaffoldSyncMapTemplate();\n    ssmt.addDirect(\n        new FileEntry(\n            DIR_SRC_1,\n            AbsoluteUnixPath.get(\"/dirDest1\"),\n            FilePermissions.DEFAULT_FILE_PERMISSIONS,\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME));\n    ssmt.addDirect(\n        new FileEntry(\n            DIR_SRC_2,\n            AbsoluteUnixPath.get(\"/dirDest2\"),\n            FilePermissions.DEFAULT_FILE_PERMISSIONS,\n            FileEntriesLayer.DEFAULT_MODIFICATION_TIME));\n    Assert.assertEquals(TEST_JSON_EMPTY_GENERATED, ssmt.getJsonString());\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/TimerEventHandlerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport com.google.cloud.tools.jib.event.events.TimerEvent;\nimport com.google.cloud.tools.jib.event.events.TimerEvent.State;\nimport java.time.Duration;\nimport java.util.ArrayDeque;\nimport java.util.Deque;\nimport java.util.Optional;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link TimerEventHandler}. */\npublic class TimerEventHandlerTest {\n\n  private final Deque<String> logMessageQueue = new ArrayDeque<>();\n\n  private static final TimerEvent.Timer ROOT_TIMER = Optional::empty;\n\n  @Test\n  public void testAccept() {\n    TimerEventHandler timerEventHandler = new TimerEventHandler(logMessageQueue::add);\n\n    timerEventHandler.accept(\n        new TimerEvent(State.START, ROOT_TIMER, Duration.ZERO, Duration.ZERO, \"description\"));\n    timerEventHandler.accept(\n        new TimerEvent(State.LAP, ROOT_TIMER, Duration.ofMillis(10), Duration.ZERO, \"description\"));\n    timerEventHandler.accept(\n        new TimerEvent(\n            State.FINISHED, ROOT_TIMER, Duration.ofMillis(100), Duration.ZERO, \"description\"));\n\n    timerEventHandler.accept(\n        new TimerEvent(\n            State.LAP,\n            () -> Optional.of(ROOT_TIMER),\n            Duration.ZERO,\n            Duration.ZERO,\n            \"child description\"));\n\n    String rootStartMessage = logMessageQueue.poll();\n    Assert.assertNotNull(rootStartMessage);\n    Assert.assertEquals(\"TIMING\\tdescription\", rootStartMessage);\n\n    String rootInProgressMessage = logMessageQueue.poll();\n    Assert.assertNotNull(rootInProgressMessage);\n    Assert.assertEquals(\"TIMED\\tdescription : 10.0 ms\", rootInProgressMessage);\n\n    String rootFinishedMessage = logMessageQueue.poll();\n    Assert.assertNotNull(rootFinishedMessage);\n    Assert.assertEquals(\"TIMED\\tdescription : 100.0 ms\", rootFinishedMessage);\n\n    String childMessage = logMessageQueue.poll();\n    Assert.assertNotNull(childMessage);\n    Assert.assertEquals(\"\\tTIMED\\tchild description : 0.0 ms\", childMessage);\n\n    Assert.assertTrue(logMessageQueue.isEmpty());\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/UpdateCheckerTest.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.google.common.truth.Truth8.assertThat;\n\nimport com.google.cloud.tools.jib.api.LogEvent;\nimport com.google.cloud.tools.jib.http.TestWebServer;\nimport com.google.common.util.concurrent.Futures;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.FileTime;\nimport java.security.GeneralSecurityException;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Collections;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mockito;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link UpdateChecker}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class UpdateCheckerTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private TestWebServer testWebServer;\n  private Path configDir;\n\n  @Before\n  public void setUp()\n      throws InterruptedException, GeneralSecurityException, URISyntaxException, IOException {\n    String response = \"HTTP/1.1 200 OK\\nContent-Length:18\\n\\n{\\\"latest\\\":\\\"2.0.0\\\"}\";\n    testWebServer = new TestWebServer(false, Collections.singletonList(response), 1);\n    configDir = temporaryFolder.getRoot().toPath();\n  }\n\n  @After\n  public void tearDown() throws IOException {\n    testWebServer.close();\n  }\n\n  @Test\n  public void testPerformUpdateCheck_newVersionFound() throws IOException, InterruptedException {\n    Instant before = Instant.now();\n    Thread.sleep(100);\n    setupLastUpdateCheck();\n    Optional<String> message =\n        UpdateChecker.performUpdateCheck(\n            configDir, \"1.0.2\", testWebServer.getEndpoint(), \"tool-name\", ignored -> {});\n\n    assertThat(testWebServer.getInputRead()).contains(\"User-Agent: jib 1.0.2 tool-name\");\n    assertThat(message).hasValue(\"2.0.0\");\n    String modifiedTime =\n        new String(\n            Files.readAllBytes(configDir.resolve(\"lastUpdateCheck\")), StandardCharsets.UTF_8);\n    assertThat(Instant.parse(modifiedTime)).isGreaterThan(before);\n  }\n\n  @Test\n  public void testPerformUpdateCheck_newJsonField()\n      throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {\n    String response =\n        \"HTTP/1.1 200 OK\\nContent-Length:43\\n\\n{\\\"latest\\\":\\\"2.0.0\\\",\\\"unknownField\\\":\\\"unknown\\\"}\";\n    try (TestWebServer server = new TestWebServer(false, Collections.singletonList(response), 1)) {\n      setupLastUpdateCheck();\n      Optional<String> message =\n          UpdateChecker.performUpdateCheck(\n              configDir, \"1.0.2\", server.getEndpoint(), \"tool-name\", ignored -> {});\n\n      assertThat(message).hasValue(\"2.0.0\");\n    }\n  }\n\n  @Test\n  public void testPerformUpdateCheck_onLatest() throws IOException, InterruptedException {\n    Instant before = Instant.now();\n    Thread.sleep(100);\n    setupLastUpdateCheck();\n    Optional<String> message =\n        UpdateChecker.performUpdateCheck(\n            configDir, \"2.0.0\", testWebServer.getEndpoint(), \"tool-name\", ignored -> {});\n\n    assertThat(message).isEmpty();\n    String modifiedTime =\n        new String(\n            Files.readAllBytes(configDir.resolve(\"lastUpdateCheck\")), StandardCharsets.UTF_8);\n    assertThat(testWebServer.getInputRead()).contains(\"User-Agent: jib 2.0.0 tool-name\");\n    assertThat(Instant.parse(modifiedTime)).isGreaterThan(before);\n  }\n\n  @Test\n  public void testPerformUpdateCheck_noLastUpdateCheck() throws IOException, InterruptedException {\n    Instant before = Instant.now();\n    Thread.sleep(100);\n    Optional<String> message =\n        UpdateChecker.performUpdateCheck(\n            configDir, \"1.0.2\", testWebServer.getEndpoint(), \"tool-name\", ignored -> {});\n\n    assertThat(message).hasValue(\"2.0.0\");\n    String modifiedTime =\n        new String(\n            Files.readAllBytes(configDir.resolve(\"lastUpdateCheck\")), StandardCharsets.UTF_8);\n    assertThat(Instant.parse(modifiedTime)).isGreaterThan(before);\n  }\n\n  @Test\n  public void testPerformUpdateCheck_emptyLastUpdateCheck()\n      throws IOException, InterruptedException {\n    Files.createFile(configDir.resolve(\"lastUpdateCheck\"));\n    Instant before = Instant.now();\n    Thread.sleep(100);\n    Optional<String> message =\n        UpdateChecker.performUpdateCheck(\n            configDir, \"1.0.2\", testWebServer.getEndpoint(), \"tool-name\", ignored -> {});\n\n    assertThat(message).hasValue(\"2.0.0\");\n    String modifiedTime =\n        new String(\n            Files.readAllBytes(configDir.resolve(\"lastUpdateCheck\")), StandardCharsets.UTF_8);\n    assertThat(Instant.parse(modifiedTime)).isGreaterThan(before);\n  }\n\n  @Test\n  public void testPerformUpdateCheck_lastUpdateCheckTooSoon() throws IOException {\n    FileTime modifiedTime = FileTime.from(Instant.now().minusSeconds(12));\n    setupLastUpdateCheck();\n    Files.write(\n        configDir.resolve(\"lastUpdateCheck\"),\n        modifiedTime.toString().getBytes(StandardCharsets.UTF_8));\n    Optional<String> message =\n        UpdateChecker.performUpdateCheck(\n            configDir, \"1.0.2\", testWebServer.getEndpoint(), \"tool-name\", ignored -> {});\n\n    assertThat(message).isEmpty();\n\n    // lastUpdateCheck should not have changed\n    String lastUpdateTime =\n        new String(\n            Files.readAllBytes(configDir.resolve(\"lastUpdateCheck\")), StandardCharsets.UTF_8);\n    assertThat(modifiedTime.toInstant()).isEqualTo(Instant.parse(lastUpdateTime));\n  }\n\n  @Test\n  public void testPerformUpdateCheck_badLastUpdateTime() throws IOException, InterruptedException {\n    Instant before = Instant.now();\n    Thread.sleep(100);\n    Files.write(\n        configDir.resolve(\"lastUpdateCheck\"), \"bad timestamp\".getBytes(StandardCharsets.UTF_8));\n    Optional<String> message =\n        UpdateChecker.performUpdateCheck(\n            configDir, \"1.0.2\", testWebServer.getEndpoint(), \"tool-name\", ignored -> {});\n    String modifiedTime =\n        new String(\n            Files.readAllBytes(configDir.resolve(\"lastUpdateCheck\")), StandardCharsets.UTF_8);\n\n    assertThat(Instant.parse(modifiedTime)).isGreaterThan(before);\n    assertThat(message).hasValue(\"2.0.0\");\n  }\n\n  @Test\n  public void testPerformUpdateCheck_failSilently()\n      throws InterruptedException, GeneralSecurityException, URISyntaxException, IOException {\n    String response = \"HTTP/1.1 400 Bad Request\\nContent-Length: 0\\n\\n\";\n    try (TestWebServer badServer =\n        new TestWebServer(false, Collections.singletonList(response), 1)) {\n      Optional<String> message =\n          UpdateChecker.performUpdateCheck(\n              configDir,\n              \"1.0.2\",\n              badServer.getEndpoint(),\n              \"tool-name\",\n              logEvent -> {\n                assertThat(logEvent.getLevel()).isEqualTo(LogEvent.Level.DEBUG);\n                assertThat(logEvent.getMessage()).contains(\"Update check failed; \");\n              });\n      assertThat(message).isEmpty();\n    }\n  }\n\n  @Test\n  public void testFinishUpdateCheck_success() {\n    Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.of(\"Hello\"));\n    Optional<String> result = UpdateChecker.finishUpdateCheck(updateCheckFuture);\n    assertThat(result).hasValue(\"Hello\");\n  }\n\n  @Test\n  public void testFinishUpdateCheck_notDone() {\n    @SuppressWarnings(\"unchecked\")\n    Future<Optional<String>> updateCheckFuture = Mockito.mock(Future.class);\n    Mockito.when(updateCheckFuture.isDone()).thenReturn(false);\n\n    Optional<String> result = UpdateChecker.finishUpdateCheck(updateCheckFuture);\n    assertThat(result).isEmpty();\n  }\n\n  private void setupLastUpdateCheck() throws IOException {\n    Files.write(\n        configDir.resolve(\"lastUpdateCheck\"),\n        Instant.now().minus(Duration.ofDays(2)).toString().getBytes(StandardCharsets.UTF_8));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/VersionCheckerTest.java",
    "content": "/*\n * Copyright 2019 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport org.junit.Assert;\nimport org.junit.Before;\nimport org.junit.Test;\n\n/** Tests for {@link VersionChecker}. */\npublic class VersionCheckerTest {\n  private static class TestVersion implements Comparable<TestVersion> {\n    private int[] components;\n\n    TestVersion(String version) {\n      String[] parts = version.split(\"\\\\.\", -1);\n      components = new int[parts.length];\n      for (int i = 0; i < parts.length; i++) {\n        components[i] = Integer.parseInt(parts[i]);\n      }\n    }\n\n    @Override\n    public int compareTo(TestVersion other) {\n      for (int i = 0; i < Math.max(components.length, other.components.length); i++) {\n        int a = i < components.length ? components[i] : 0;\n        int b = i < other.components.length ? other.components[i] : 0;\n        if (a < b) {\n          return -1;\n        } else if (a > b) {\n          return 1;\n        }\n      }\n      return 0;\n    }\n  }\n\n  private VersionChecker<TestVersion> checker;\n\n  @Before\n  public void setUp() {\n    Assert.assertTrue(new TestVersion(\"1.0\").compareTo(new TestVersion(\"1.1.1\")) < 0);\n    Assert.assertTrue(new TestVersion(\"1.1.1\").compareTo(new TestVersion(\"1.0\")) > 0);\n    Assert.assertTrue(new TestVersion(\"1.1\").compareTo(new TestVersion(\"1.1.0.0\")) == 0);\n    checker = new VersionChecker<>(TestVersion::new);\n  }\n\n  @Test\n  public void testComparators_LT() {\n    Assert.assertTrue(VersionChecker.lt(0, 1));\n    Assert.assertFalse(VersionChecker.lt(1, 1));\n    Assert.assertFalse(VersionChecker.lt(2, 1));\n  }\n\n  @Test\n  public void testComparators_LE() {\n    Assert.assertTrue(VersionChecker.le(0, 1));\n    Assert.assertTrue(VersionChecker.le(1, 1));\n    Assert.assertFalse(VersionChecker.le(2, 1));\n  }\n\n  @Test\n  public void testComparators_GE() {\n    Assert.assertFalse(VersionChecker.ge(0, 1));\n    Assert.assertTrue(VersionChecker.ge(1, 1));\n    Assert.assertTrue(VersionChecker.ge(2, 1));\n  }\n\n  @Test\n  public void testComparators_GT() {\n    Assert.assertFalse(VersionChecker.gt(0, 1));\n    Assert.assertFalse(VersionChecker.gt(1, 1));\n    Assert.assertTrue(VersionChecker.gt(2, 1));\n  }\n\n  @Test\n  public void testRange_leftClosed() {\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,4.3]\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,4.3)\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,)\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,]\", \"1.0\"));\n  }\n\n  @Test\n  public void testRange_leftClosed_exact() {\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,4.3]\", \"2.3\"));\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,4.3)\", \"2.3\"));\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,)\", \"2.3\"));\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,]\", \"2.3\"));\n  }\n\n  @Test\n  public void testRange_leftOpen() {\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3]\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3)\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,)\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,]\", \"1.0\"));\n  }\n\n  @Test\n  public void testRange_leftOpen_exact() {\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3]\", \"2.3\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3)\", \"2.3\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,)\", \"2.3\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,]\", \"2.3\"));\n  }\n\n  @Test\n  public void testRange_rightClosed() {\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,4.3]\", \"5.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3]\", \"5.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"[,4.3]\", \"5.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(,4.3]\", \"5.0\"));\n  }\n\n  @Test\n  public void testRange_rightClosed_exact() {\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,4.3]\", \"4.3\"));\n    Assert.assertTrue(checker.compatibleVersion(\"(2.3,4.3]\", \"4.3\"));\n    Assert.assertTrue(checker.compatibleVersion(\"[,4.3]\", \"4.3\"));\n    Assert.assertTrue(checker.compatibleVersion(\"(,4.3]\", \"4.3\"));\n  }\n\n  @Test\n  public void testRange_between() {\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,4.3]\", \"2.4\"));\n    Assert.assertTrue(checker.compatibleVersion(\"(2.3,4.3]\", \"4.2\"));\n    Assert.assertTrue(checker.compatibleVersion(\"[2.3,4.3)\", \"2.4\"));\n    Assert.assertTrue(checker.compatibleVersion(\"(2.3,4.3)\", \"4.2\"));\n  }\n\n  @Test\n  public void testRange_rightOpen() {\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,4.3)\", \"5.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3)\", \"5.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"[,4.3)\", \"5.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(,4.3)\", \"5.0\"));\n  }\n\n  @Test\n  public void testRange_rightOpen_exact() {\n    Assert.assertFalse(checker.compatibleVersion(\"[2.3,4.3)\", \"4.3\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(2.3,4.3)\", \"4.3\"));\n    Assert.assertFalse(checker.compatibleVersion(\"[,4.3)\", \"4.3\"));\n    Assert.assertFalse(checker.compatibleVersion(\"(,4.3)\", \"4.3\"));\n  }\n\n  @Test\n  public void testMinimumBound_low() {\n    Assert.assertFalse(checker.compatibleVersion(\"2.3\", \"1.0\"));\n    Assert.assertFalse(checker.compatibleVersion(\"2.3\", \"2.2\"));\n  }\n\n  @Test\n  public void testMinimumBound_exact() {\n    Assert.assertTrue(checker.compatibleVersion(\"2.3\", \"2.3\"));\n  }\n\n  @Test\n  public void testMinimumBound_high() {\n    Assert.assertTrue(checker.compatibleVersion(\"2.3\", \"2.4\"));\n    Assert.assertTrue(checker.compatibleVersion(\"2.3\", \"4.0\"));\n  }\n\n  // @SuppressWarnings({\"TryFailThrowable\", \"AssertionFailureIgnored\"})\n  @Test\n  public void testRange_invalid() {\n    for (String rangeSpec :\n        new String[] {\"[]\", \"[,]\", \"(,]\", \"[,)\", \"(,)\", \"[1,2,3]\", \"[1]\", \"foo\", \"{,2.3)\", \"\"}) {\n      try {\n        checker.compatibleVersion(rangeSpec, \"1.3\");\n        Assert.fail(\"should have thrown an exception for \" + rangeSpec);\n      } catch (IllegalArgumentException ex) {\n        // as expected\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ZipUtilTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.common.io.Resources;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileTime;\nimport java.time.Instant;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.Assert;\nimport org.junit.Assume;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link ZipUtil}. */\npublic class ZipUtilTest {\n\n  @Rule public final TemporaryFolder tempFolder = new TemporaryFolder();\n\n  @Test\n  public void testUnzip() throws URISyntaxException, IOException {\n    verifyUnzip(tempFolder.getRoot().toPath());\n  }\n\n  @Test\n  public void testUnzip_nonExistingDestination() throws URISyntaxException, IOException {\n    Path destination = tempFolder.getRoot().toPath().resolve(\"non/exisiting\");\n    verifyUnzip(destination);\n\n    Assert.assertTrue(Files.exists(destination));\n  }\n\n  @Test\n  public void testZipSlipVulnerability_windows() throws URISyntaxException {\n    Assume.assumeTrue(System.getProperty(\"os.name\").startsWith(\"Windows\"));\n\n    Path archive =\n        Paths.get(Resources.getResource(\"plugins-common/test-archives/zip-slip-win.zip\").toURI());\n    verifyZipSlipSafe(archive);\n  }\n\n  @Test\n  public void testZipSlipVulnerability_unix() throws URISyntaxException {\n    Assume.assumeFalse(System.getProperty(\"os.name\").startsWith(\"Windows\"));\n\n    Path archive =\n        Paths.get(Resources.getResource(\"plugins-common/test-archives/zip-slip.zip\").toURI());\n    verifyZipSlipSafe(archive);\n  }\n\n  @Test\n  public void testUnzip_modificationTimePreserved() throws URISyntaxException, IOException {\n    Path archive =\n        Paths.get(Resources.getResource(\"plugins-common/test-archives/test.zip\").toURI());\n    Path destination = tempFolder.getRoot().toPath();\n\n    ZipUtil.unzip(archive, destination);\n\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"file1.txt\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T14:53:05Z\")));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"my-zip/file2.txt\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T14:53:44Z\")));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"my-zip\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T15:15:48Z\")));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"my-zip/some\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T14:53:38Z\")));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"my-zip/some/sub\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T14:53:38Z\")));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"my-zip/some/sub/folder\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T15:16:11Z\")));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"my-zip/some/sub/folder/file3.txt\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2018-08-30T15:16:12Z\")));\n  }\n\n  @Test\n  public void testUnzip_reproducibleTimestampsEnabled() throws URISyntaxException, IOException {\n    // The zipfile has only level1/level2/level3/file.txt packaged\n    Path archive =\n        Paths.get(\n            Resources.getResource(\"plugins-common/test-archives/zip-only-file-packaged.zip\")\n                .toURI());\n\n    Path destination = tempFolder.getRoot().toPath();\n\n    ZipUtil.unzip(archive, destination, true);\n\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"level-1\")))\n        .isEqualTo(FileTime.fromMillis(1000L));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"level-1/level-2\")))\n        .isEqualTo(FileTime.fromMillis(1000L));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"level-1/level-2/level-3\")))\n        .isEqualTo(FileTime.fromMillis(1000L));\n    assertThat(Files.getLastModifiedTime(destination.resolve(\"level-1/level-2/level-3/file.txt\")))\n        .isEqualTo(FileTime.from(Instant.parse(\"2021-01-29T21:10:02Z\")));\n  }\n\n  @Test\n  public void testUnzip_reproducibleTimestampsEnabled_destinationNotEmpty() throws IOException {\n    Path destination = tempFolder.getRoot().toPath();\n    tempFolder.newFile();\n\n    IllegalStateException exception =\n        assertThrows(\n            IllegalStateException.class,\n            () -> ZipUtil.unzip(Paths.get(\"ignore\"), destination, true));\n    assertThat(exception).hasMessageThat().startsWith(\"Cannot enable reproducible timestamps\");\n  }\n\n  private void verifyUnzip(Path destination) throws URISyntaxException, IOException {\n    Path archive =\n        Paths.get(Resources.getResource(\"plugins-common/test-archives/test.zip\").toURI());\n\n    ZipUtil.unzip(archive, destination);\n\n    Assert.assertTrue(Files.isDirectory(destination.resolve(\"my-zip/some/sub/folder\")));\n\n    Path file1 = destination.resolve(\"file1.txt\");\n    Path file2 = destination.resolve(\"my-zip/file2.txt\");\n    Path file3 = destination.resolve(\"my-zip/some/sub/folder/file3.txt\");\n    Assert.assertEquals(\"file1\", Files.readAllLines(file1).get(0));\n    Assert.assertEquals(\"file2\", Files.readAllLines(file2).get(0));\n    Assert.assertEquals(\"file3\", Files.readAllLines(file3).get(0));\n  }\n\n  private void verifyZipSlipSafe(Path archive) {\n    try {\n      ZipUtil.unzip(archive, tempFolder.getRoot().toPath());\n      Assert.fail(\"Should block Zip-Slip\");\n    } catch (IOException ex) {\n      MatcherAssert.assertThat(\n          ex.getMessage(),\n          CoreMatchers.startsWith(\"Blocked unzipping files outside destination: \"));\n    }\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/globalconfig/GlobalConfigTest.java",
    "content": "/*\n * Copyright 2021 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.globalconfig;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.assertThrows;\n\nimport com.google.cloud.tools.jib.plugins.common.PropertyNames;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.contrib.java.lang.system.RestoreSystemProperties;\nimport org.junit.rules.TemporaryFolder;\n\n/** Tests for {@link GlobalConfig}. */\npublic class GlobalConfigTest {\n\n  @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();\n  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();\n\n  private Path configDir;\n\n  @Before\n  public void setUp() {\n    configDir = temporaryFolder.getRoot().toPath();\n  }\n\n  @Test\n  public void testReadConfig_default() throws IOException, InvalidGlobalConfigException {\n    GlobalConfig globalConfig = GlobalConfig.readConfig(configDir);\n\n    assertThat(globalConfig.isDisableUpdateCheck()).isFalse();\n    assertThat(globalConfig.getRegistryMirrors()).isEmpty();\n  }\n\n  @Test\n  public void testReadConfig_newConfigCreated() throws IOException, InvalidGlobalConfigException {\n    GlobalConfig.readConfig(configDir);\n    String configJson =\n        new String(Files.readAllBytes(configDir.resolve(\"config.json\")), StandardCharsets.UTF_8);\n    assertThat(configJson).isEqualTo(\"{\\\"disableUpdateCheck\\\":false,\\\"registryMirrors\\\":[]}\");\n  }\n\n  @Test\n  public void testReadConfig_emptyJson() throws IOException, InvalidGlobalConfigException {\n    Files.write(configDir.resolve(\"config.json\"), \"{}\".getBytes(StandardCharsets.UTF_8));\n    GlobalConfig globalConfig = GlobalConfig.readConfig(configDir);\n\n    assertThat(globalConfig.isDisableUpdateCheck()).isFalse();\n    assertThat(globalConfig.getRegistryMirrors()).isEmpty();\n  }\n\n  @Test\n  public void testReadConfig() throws IOException, InvalidGlobalConfigException {\n    String json =\n        \"{\\\"disableUpdateCheck\\\":true, \\\"registryMirrors\\\":[\"\n            + \"{ \\\"registry\\\": \\\"registry-1.docker.io\\\",\"\n            + \"  \\\"mirrors\\\": [\\\"mirror.gcr.io\\\", \\\"localhost:5000\\\"] },\"\n            + \"{ \\\"registry\\\": \\\"another.registry\\\", \\\"mirrors\\\": [\\\"another.mirror\\\"] }\"\n            + \"]}\";\n    Files.write(configDir.resolve(\"config.json\"), json.getBytes(StandardCharsets.UTF_8));\n\n    GlobalConfig globalConfig = GlobalConfig.readConfig(configDir);\n    assertThat(globalConfig.isDisableUpdateCheck()).isTrue();\n    assertThat(globalConfig.getRegistryMirrors())\n        .containsExactly(\n            \"registry-1.docker.io\",\n            \"mirror.gcr.io\",\n            \"registry-1.docker.io\",\n            \"localhost:5000\",\n            \"another.registry\",\n            \"another.mirror\");\n  }\n\n  @Test\n  public void testReadConfig_systemProperties() throws IOException, InvalidGlobalConfigException {\n    Files.write(\n        configDir.resolve(\"config.json\"),\n        \"{\\\"disableUpdateCheck\\\":false}\".getBytes(StandardCharsets.UTF_8));\n\n    GlobalConfig globalConfig = GlobalConfig.readConfig(configDir);\n    System.setProperty(PropertyNames.DISABLE_UPDATE_CHECKS, \"true\");\n    assertThat(globalConfig.isDisableUpdateCheck()).isTrue();\n  }\n\n  @Test\n  public void testReadConfig_emptyFile() throws IOException {\n    temporaryFolder.newFile(\"config.json\");\n    IOException exception =\n        assertThrows(IOException.class, () -> GlobalConfig.readConfig(configDir));\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\n            \"Failed to create, open, or parse global Jib config file; see \"\n                + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it \"\n                + \"to fix or you may need to delete\");\n    assertThat(exception).hasMessageThat().endsWith(File.separator + \"config.json\");\n  }\n\n  @Test\n  public void testReadConfig_corrupted() throws IOException {\n    Files.write(\n        configDir.resolve(\"config.json\"), \"corrupt config\".getBytes(StandardCharsets.UTF_8));\n    IOException exception =\n        assertThrows(IOException.class, () -> GlobalConfig.readConfig(configDir));\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\n            \"Failed to create, open, or parse global Jib config file; see \"\n                + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it \"\n                + \"to fix or you may need to delete \");\n    assertThat(exception).hasMessageThat().endsWith(File.separator + \"config.json\");\n  }\n\n  @Test\n  public void testReadConfig_missingRegistry() throws IOException {\n    String json = \"{\\\"registryMirrors\\\":[{\\\"mirrors\\\":[\\\"mirror.gcr.io\\\"]}]}\";\n    Files.write(configDir.resolve(\"config.json\"), json.getBytes(StandardCharsets.UTF_8));\n    InvalidGlobalConfigException exception =\n        assertThrows(InvalidGlobalConfigException.class, () -> GlobalConfig.readConfig(configDir));\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\n            \"'registryMirrors.registry' property is missing; see \"\n                + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it \"\n                + \"to fix or you may need to delete \");\n  }\n\n  @Test\n  public void testReadConfig_missingMirrors() throws IOException {\n    String json = \"{\\\"registryMirrors\\\":[{\\\"registry\\\": \\\"registry\\\"}]}\";\n    Files.write(configDir.resolve(\"config.json\"), json.getBytes(StandardCharsets.UTF_8));\n    InvalidGlobalConfigException exception =\n        assertThrows(InvalidGlobalConfigException.class, () -> GlobalConfig.readConfig(configDir));\n    assertThat(exception)\n        .hasMessageThat()\n        .startsWith(\n            \"'registryMirrors.mirrors' property is missing; see \"\n                + \"https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#where-is-the-global-jib-configuration-file-and-how-i-can-configure-it \"\n                + \"to fix or you may need to delete\");\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/logging/AnsiLoggerWithFooterTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.common.collect.ImmutableMap;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link AnsiLoggerWithFooter}. */\npublic class AnsiLoggerWithFooterTest {\n\n  private static final Duration SHUTDOWN_TIMEOUT = Duration.ofSeconds(3);\n\n  private final SingleThreadedExecutor singleThreadedExecutor = new SingleThreadedExecutor();\n\n  private final List<String> messages = new ArrayList<>();\n  private final List<Level> levels = new ArrayList<>();\n\n  @Test\n  public void testTruncateToMaxWidth() {\n    List<String> lines =\n        Arrays.asList(\n            \"this line of text is way too long and will be truncated\",\n            \"this line will not be truncated\");\n    Assert.assertEquals(\n        Arrays.asList(\n            \"this line of text is way too long and will be t...\",\n            \"this line will not be truncated\"),\n        AnsiLoggerWithFooter.truncateToMaxWidth(lines));\n  }\n\n  @Test\n  public void testNoLifecycle() {\n    try {\n      new AnsiLoggerWithFooter(ImmutableMap.of(), singleThreadedExecutor, false);\n      Assert.fail();\n\n    } catch (IllegalArgumentException ex) {\n      Assert.assertEquals(\n          \"Cannot construct AnsiLoggerFooter without LIFECYCLE message consumer\", ex.getMessage());\n    }\n  }\n\n  @Test\n  public void testLog_noFooter() {\n    AnsiLoggerWithFooter testAnsiLoggerWithFooter = createTestLogger(false);\n    testAnsiLoggerWithFooter.log(Level.LIFECYCLE, \"lifecycle\");\n    testAnsiLoggerWithFooter.log(Level.PROGRESS, \"progress\");\n    testAnsiLoggerWithFooter.log(Level.INFO, \"info\");\n    testAnsiLoggerWithFooter.log(Level.DEBUG, \"debug\");\n    testAnsiLoggerWithFooter.log(Level.WARN, \"warn\");\n    testAnsiLoggerWithFooter.log(Level.ERROR, \"error\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(\n        Arrays.asList(\"lifecycle\", \"progress\", \"info\", \"debug\", \"warn\", \"error\"), messages);\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE, Level.PROGRESS, Level.INFO, Level.DEBUG, Level.WARN, Level.ERROR),\n        levels);\n  }\n\n  @Test\n  public void testLog_ignoreIfNoMessageConsumer() {\n    AnsiLoggerWithFooter testAnsiLoggerWithFooter =\n        new AnsiLoggerWithFooter(\n            ImmutableMap.of(Level.LIFECYCLE, createMessageConsumer(Level.LIFECYCLE)),\n            singleThreadedExecutor,\n            false);\n\n    testAnsiLoggerWithFooter.log(Level.LIFECYCLE, \"lifecycle\");\n    testAnsiLoggerWithFooter.log(Level.PROGRESS, \"progress\");\n    testAnsiLoggerWithFooter.log(Level.INFO, \"info\");\n    testAnsiLoggerWithFooter.log(Level.DEBUG, \"debug\");\n    testAnsiLoggerWithFooter.log(Level.WARN, \"warn\");\n    testAnsiLoggerWithFooter.log(Level.ERROR, \"error\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(Collections.singletonList(\"lifecycle\"), messages);\n    Assert.assertEquals(Collections.singletonList(Level.LIFECYCLE), levels);\n  }\n\n  @Test\n  public void testLog_sameFooter() {\n    AnsiLoggerWithFooter testAnsiLoggerWithFooter = createTestLogger(false);\n    testAnsiLoggerWithFooter.setFooter(Collections.singletonList(\"footer\"));\n    testAnsiLoggerWithFooter.log(Level.INFO, \"message\");\n    testAnsiLoggerWithFooter.log(Level.INFO, \"another message\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(\n        Arrays.asList(\n            \"\\033[1mfooter\\033[0m\", // single-line footer in bold\n\n            // now triggered by logging a message\n            \"\\033[1A\\033[0J\", // cursor up and erase to the end\n            \"\\033[1Amessage\", // cursor up + message\n            \"\\033[1mfooter\\033[0m\", // footer\n\n            // by logging another message\n            \"\\033[1A\\033[0J\", // cursor up and erase\n            \"\\033[1Aanother message\", // cursor up + message\n            \"\\033[1mfooter\\033[0m\"), // footer\n        messages);\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.INFO,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.INFO,\n            Level.LIFECYCLE),\n        levels);\n  }\n\n  @Test\n  public void testLog_sameFooterWithEnableTwoCursorUpJump() {\n    AnsiLoggerWithFooter testAnsiLoggerWithFooter = createTestLogger(true);\n    testAnsiLoggerWithFooter.setFooter(Collections.singletonList(\"footer\"));\n    testAnsiLoggerWithFooter.log(Level.INFO, \"message\");\n    testAnsiLoggerWithFooter.log(Level.INFO, \"another message\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(\n        Arrays.asList(\n            \"\\033[1mfooter\\033[0m\", // single-line footer in bold\n\n            // now triggered by logging a message\n            \"\\033[1A\\033[0J\", // cursor up and erase to the end\n            \"\\033[2A\", // up two lines\n            \"message\",\n            \"\\033[1mfooter\\033[0m\", // footer\n\n            // by logging another message\n            \"\\033[1A\\033[0J\", // cursor up and erase\n            \"\\033[2A\", // up two\n            \"another message\",\n            \"\\033[1mfooter\\033[0m\"), // footer\n        messages);\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.INFO,\n            Level.INFO,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.INFO,\n            Level.INFO,\n            Level.LIFECYCLE),\n        levels);\n  }\n\n  @Test\n  public void testLog_changingFooter() {\n    AnsiLoggerWithFooter testAnsiLoggerWithFooter = createTestLogger(false);\n    testAnsiLoggerWithFooter.setFooter(Collections.singletonList(\"footer\"));\n    testAnsiLoggerWithFooter.log(Level.WARN, \"message\");\n    testAnsiLoggerWithFooter.setFooter(Arrays.asList(\"two line\", \"footer\"));\n    testAnsiLoggerWithFooter.log(Level.WARN, \"another message\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(\n        Arrays.asList(\n            \"\\033[1mfooter\\033[0m\", // single-line footer in bold\n\n            // now triggered by logging a warning\n            \"\\033[1A\\033[0J\", // cursor up and erase to the end\n            \"\\033[1Amessage\", // cursor up + message\n            \"\\033[1mfooter\\033[0m\", // footer\n\n            // by setting a two-line footer\n            \"\\033[1A\\033[0J\", // cursor up and erase\n            \"\\033[1A\\033[1mtwo line\\033[0m\", // cursor up + footer line 1\n            \"\\033[1mfooter\\033[0m\", // footer line 2\n\n            // by logging another warning\n            \"\\033[2A\\033[0J\", // cursor up twice (to erase two-line footer) and erase\n            \"\\033[1Aanother message\", // cursor up + message\n            \"\\033[1mtwo line\\033[0m\", // footer line 1\n            \"\\033[1mfooter\\033[0m\"), // footer line 2\n        messages);\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.WARN,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.WARN,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE),\n        levels);\n  }\n\n  @Test\n  public void testLog_changingFooterWithEnableTwoCursorUpJump() {\n    AnsiLoggerWithFooter testAnsiLoggerWithFooter = createTestLogger(true);\n    testAnsiLoggerWithFooter.setFooter(Collections.singletonList(\"footer\"));\n    testAnsiLoggerWithFooter.log(Level.WARN, \"message\");\n    testAnsiLoggerWithFooter.setFooter(Arrays.asList(\"two line\", \"footer\"));\n    testAnsiLoggerWithFooter.log(Level.WARN, \"another message\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(\n        Arrays.asList(\n            \"\\033[1mfooter\\033[0m\", // single-line footer in bold\n\n            // now triggered by logging a warning\n            \"\\033[1A\\033[0J\", // cursor up and erase to the end\n            \"\\033[2A\", // up two lines\n            \"message\",\n            \"\\033[1mfooter\\033[0m\", // footer\n\n            // by setting a two-line footer\n            \"\\033[1A\\033[0J\", // cursor up and erase\n            \"\\033[2A\", // up two lines\n            \"\\033[1mtwo line\\033[0m\", // footer line 1\n            \"\\033[1mfooter\\033[0m\", // footer line 2\n\n            // by logging another warning\n            \"\\033[2A\\033[0J\", // cursor up twice (to erase two-line footer) and erase\n            \"\\033[2A\", // up two lines\n            \"another message\",\n            \"\\033[1mtwo line\\033[0m\", // footer line 1\n            \"\\033[1mfooter\\033[0m\"), // footer line 2\n        messages);\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.WARN,\n            Level.WARN,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE,\n            Level.WARN,\n            Level.WARN,\n            Level.LIFECYCLE,\n            Level.LIFECYCLE),\n        levels);\n  }\n\n  private AnsiLoggerWithFooter createTestLogger(boolean enableTwoCursorUpJump) {\n    ImmutableMap.Builder<Level, Consumer<String>> messageConsumers = ImmutableMap.builder();\n    for (Level level : Level.values()) {\n      messageConsumers.put(level, createMessageConsumer(level));\n    }\n\n    return new AnsiLoggerWithFooter(\n        messageConsumers.build(), singleThreadedExecutor, enableTwoCursorUpJump);\n  }\n\n  private Consumer<String> createMessageConsumer(Level level) {\n    return message -> {\n      levels.add(level);\n      messages.add(message);\n    };\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/logging/ConsoleLoggerBuilderTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.cloud.tools.jib.plugins.common.logging.ConsoleLoggerBuilder.ConsoleLoggerFactory;\nimport com.google.common.collect.ImmutableMap;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.MockitoJUnitRunner;\n\n/** Tests for {@link ConsoleLoggerBuilder}. */\n@RunWith(MockitoJUnitRunner.class)\npublic class ConsoleLoggerBuilderTest {\n\n  @Mock private Consumer<String> mockLifecycleConsumer;\n  @Mock private Consumer<String> mockProgressConsumer;\n  @Mock private Consumer<String> mockInfoConsumer;\n  @Mock private Consumer<String> mockDebugConsumer;\n  @Mock private Consumer<String> mockWarnConsumer;\n  @Mock private Consumer<String> mockErrorConsumer;\n\n  @Test\n  public void testBuild() {\n    List<String> messages = new ArrayList<>();\n    List<Level> levels = new ArrayList<>();\n\n    ConsoleLoggerFactory consoleLoggerFactory =\n        messageConsumers -> {\n          Assert.assertEquals(\n              ImmutableMap.builder()\n                  .put(Level.LIFECYCLE, mockLifecycleConsumer)\n                  .put(Level.PROGRESS, mockProgressConsumer)\n                  .put(Level.INFO, mockInfoConsumer)\n                  .put(Level.DEBUG, mockDebugConsumer)\n                  .put(Level.WARN, mockWarnConsumer)\n                  .put(Level.ERROR, mockErrorConsumer)\n                  .build(),\n              messageConsumers);\n\n          return new ConsoleLogger() {\n\n            @Override\n            public void log(Level logLevel, String message) {\n              messages.add(message);\n              levels.add(logLevel);\n            }\n\n            @Override\n            public void setFooter(List<String> footerLines) {\n              // No op.\n            }\n          };\n        };\n\n    ConsoleLogger consoleLogger =\n        new ConsoleLoggerBuilder(consoleLoggerFactory)\n            .lifecycle(mockLifecycleConsumer)\n            .progress(mockProgressConsumer)\n            .info(mockInfoConsumer)\n            .debug(mockDebugConsumer)\n            .warn(mockWarnConsumer)\n            .error(mockErrorConsumer)\n            .build();\n\n    consoleLogger.log(Level.LIFECYCLE, \"lifecycle\");\n    consoleLogger.log(Level.PROGRESS, \"progress\");\n    consoleLogger.log(Level.INFO, \"info\");\n    consoleLogger.log(Level.DEBUG, \"debug\");\n    consoleLogger.log(Level.WARN, \"warn\");\n    consoleLogger.log(Level.ERROR, \"error\");\n\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE, Level.PROGRESS, Level.INFO, Level.DEBUG, Level.WARN, Level.ERROR),\n        levels);\n    Assert.assertEquals(\n        Arrays.asList(\"lifecycle\", \"progress\", \"info\", \"debug\", \"warn\", \"error\"), messages);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/logging/PlainConsoleLoggerTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.api.LogEvent.Level;\nimport com.google.common.collect.ImmutableMap;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link PlainConsoleLogger}. */\npublic class PlainConsoleLoggerTest {\n\n  private static final Duration SHUTDOWN_TIMEOUT = Duration.ofSeconds(3);\n\n  private final SingleThreadedExecutor singleThreadedExecutor = new SingleThreadedExecutor();\n\n  private final List<Level> levels = new ArrayList<>();\n  private final List<String> messages = new ArrayList<>();\n\n  private PlainConsoleLogger testPlainConsoleLogger;\n\n  @Test\n  public void testLog() {\n    ImmutableMap.Builder<Level, Consumer<String>> messageConsumers = ImmutableMap.builder();\n    for (Level level : Level.values()) {\n      messageConsumers.put(level, createMessageConsumer(level));\n    }\n\n    testPlainConsoleLogger =\n        new PlainConsoleLogger(messageConsumers.build(), singleThreadedExecutor);\n\n    testPlainConsoleLogger.log(Level.LIFECYCLE, \"lifecycle\");\n    testPlainConsoleLogger.log(Level.PROGRESS, \"progress\");\n    testPlainConsoleLogger.log(Level.INFO, \"info\");\n    testPlainConsoleLogger.log(Level.DEBUG, \"debug\");\n    testPlainConsoleLogger.log(Level.WARN, \"warn\");\n    testPlainConsoleLogger.log(Level.ERROR, \"error\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(\n        Arrays.asList(\n            Level.LIFECYCLE, Level.PROGRESS, Level.INFO, Level.DEBUG, Level.WARN, Level.ERROR),\n        levels);\n    Assert.assertEquals(\n        Arrays.asList(\"lifecycle\", \"progress\", \"info\", \"debug\", \"warn\", \"error\"), messages);\n  }\n\n  @Test\n  public void testLog_filterOutColors() {\n    ImmutableMap.Builder<Level, Consumer<String>> messageConsumers = ImmutableMap.builder();\n    for (Level level : Level.values()) {\n      messageConsumers.put(level, createMessageConsumer(level));\n    }\n\n    testPlainConsoleLogger =\n        new PlainConsoleLogger(messageConsumers.build(), singleThreadedExecutor);\n\n    testPlainConsoleLogger.log(Level.LIFECYCLE, \"\\u001B[36;1mlifecycle\\u001B[0m\");\n    testPlainConsoleLogger.log(Level.PROGRESS, \"\\u001B[33mprogress\\u001B[0m\");\n    testPlainConsoleLogger.log(Level.ERROR, \"\\u001B[31;1merror\\u001B[0m\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(Arrays.asList(Level.LIFECYCLE, Level.PROGRESS, Level.ERROR), levels);\n    Assert.assertEquals(Arrays.asList(\"lifecycle\", \"progress\", \"error\"), messages);\n  }\n\n  @Test\n  public void testLog_ignoreIfNoMessageConsumer() {\n    testPlainConsoleLogger =\n        new PlainConsoleLogger(\n            ImmutableMap.of(Level.WARN, createMessageConsumer(Level.WARN)), singleThreadedExecutor);\n\n    testPlainConsoleLogger.log(Level.LIFECYCLE, \"lifecycle\");\n    testPlainConsoleLogger.log(Level.PROGRESS, \"progress\");\n    testPlainConsoleLogger.log(Level.INFO, \"info\");\n    testPlainConsoleLogger.log(Level.DEBUG, \"debug\");\n    testPlainConsoleLogger.log(Level.WARN, \"warn\");\n    testPlainConsoleLogger.log(Level.ERROR, \"error\");\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n\n    Assert.assertEquals(Collections.singletonList(Level.WARN), levels);\n    Assert.assertEquals(Collections.singletonList(\"warn\"), messages);\n  }\n\n  private Consumer<String> createMessageConsumer(Level level) {\n    return message -> {\n      levels.add(level);\n      messages.add(message);\n    };\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/logging/ProgressDisplayGeneratorTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link ProgressDisplayGenerator}. */\npublic class ProgressDisplayGeneratorTest {\n\n  private static String getBar(String bar, double value) {\n    return String.format(\"%s %.1f%% complete\", bar, value);\n  }\n\n  @Test\n  public void testGenerateProgressDisplay_progressBar_0() {\n    Assert.assertEquals(\n        Arrays.asList(\"Executing tasks:\", getBar(\"[                              ]\", 0.0)),\n        ProgressDisplayGenerator.generateProgressDisplay(0, Collections.emptyList()));\n  }\n\n  @Test\n  public void testGenerateProgressDisplay_progressBar_50() {\n    Assert.assertEquals(\n        Arrays.asList(\"Executing tasks:\", getBar(\"[===============               ]\", 50.0)),\n        ProgressDisplayGenerator.generateProgressDisplay(0.5, Collections.emptyList()));\n  }\n\n  @Test\n  public void testGenerateProgressDisplay_progressBar_100() {\n    Assert.assertEquals(\n        Arrays.asList(\"Executing tasks:\", getBar(\"[==============================]\", 100.0)),\n        ProgressDisplayGenerator.generateProgressDisplay(1, Collections.emptyList()));\n  }\n\n  @Test\n  public void testGenerateProgressDisplay_unfinishedTasks() {\n    Assert.assertEquals(\n        Arrays.asList(\n            \"Executing tasks:\",\n            getBar(\"[===============               ]\", 50.0),\n            \"> unfinished task\",\n            \"> another task in progress\",\n            \"> stalled\"),\n        ProgressDisplayGenerator.generateProgressDisplay(\n            0.5, Arrays.asList(\"unfinished task\", \"another task in progress\", \"stalled\")));\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/logging/SingleThreadedExecutorTest.java",
    "content": "/*\n * Copyright 2018 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.common.logging;\n\nimport com.google.cloud.tools.jib.MultithreadedExecutor;\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport org.junit.Assert;\nimport org.junit.Test;\n\n/** Tests for {@link SingleThreadedExecutor}. */\npublic class SingleThreadedExecutorTest {\n\n  private static final Duration SHUTDOWN_TIMEOUT = Duration.ofSeconds(3);\n\n  @SuppressWarnings(\"ThreadPriorityCheck\") // use of Thread.yield()\n  @Test\n  public void testExecute_mutualExclusion()\n      throws IOException, ExecutionException, InterruptedException {\n    SingleThreadedExecutor singleThreadedExecutor = new SingleThreadedExecutor();\n    Lock lock = new ReentrantLock();\n\n    try (MultithreadedExecutor multithreadedExecutor = new MultithreadedExecutor()) {\n      multithreadedExecutor.invokeAll(\n          Collections.nCopies(\n              100,\n              () -> {\n                singleThreadedExecutor.execute(\n                    () -> {\n                      Assert.assertTrue(lock.tryLock());\n                      Thread.yield();\n                      lock.unlock();\n                    });\n                return null;\n              }));\n    }\n\n    singleThreadedExecutor.shutDownAndAwaitTermination(SHUTDOWN_TIMEOUT);\n  }\n}\n"
  },
  {
    "path": "jib-plugins-common/src/test/resources/plugins-common/exploded-war/META-INF/context.xml",
    "content": ""
  },
  {
    "path": "jib-plugins-common/src/test/resources/plugins-common/exploded-war/Test.jsp",
    "content": ""
  },
  {
    "path": "jib-plugins-common/src/test/resources/plugins-common/exploded-war/WEB-INF/classes/package/test.properties",
    "content": ""
  },
  {
    "path": "jib-plugins-common/src/test/resources/plugins-common/exploded-war/WEB-INF/web.xml",
    "content": ""
  },
  {
    "path": "jib-plugins-extension-common/build.gradle",
    "content": "plugins {\n  id 'net.researchgate.release'\n  id 'maven-publish'\n  id 'eclipse'\n}\n\njar {\n  manifest {\n    attributes 'Implementation-Version': archiveVersion\n    attributes 'Automatic-Module-Name': 'com.google.cloud.tools.jib.plugins.extension'\n\n    // OSGi metadata\n    attributes 'Bundle-SymbolicName': 'com.google.cloud.tools.jib.plugins.extension'\n    attributes 'Bundle-Name': 'Common Base for Jib Plugin Extension APIs'\n    attributes 'Bundle-Vendor': 'Google LLC'\n    attributes 'Bundle-DocURL': 'https://github.com/GoogleContainerTools/jib'\n    attributes 'Bundle-License': 'https://www.apache.org/licenses/LICENSE-2.0'\n    attributes 'Export-Package': 'com.google.cloud.tools.jib.plugins.extension'\n  }\n}\n\nconfigureMavenRelease()\n\n/* RELEASE */\npublishing {\n  publications {\n    mavenJava(MavenPublication) {\n      pom {\n        name = 'Common Base for Jib Plugin Extension APIs'\n        description = 'Internal common base for Jib Plugin Extension APIs.'\n      }\n      from components.java\n    }\n  }\n}\n\n// Release plugin (git release commits and version updates)\nrelease {\n  tagTemplate = 'v$version-extension-common'\n  git {\n    requireBranch = /^extension-common-release-v\\d+.*$/  //regex\n  }\n}\n/* RELEASE */\n\n/* ECLIPSE */\neclipse.classpath.plusConfigurations += [configurations.integrationTestImplementation]\n/* ECLIPSE */\n"
  },
  {
    "path": "jib-plugins-extension-common/gradle.properties",
    "content": "version = 0.2.1-SNAPSHOT\n"
  },
  {
    "path": "jib-plugins-extension-common/kokoro/release_build.sh",
    "content": "#!/bin/bash\n\n# Fail on any error.\nset -o errexit\n# Display commands to stderr.\nset -o xtrace\n\ncd github/jib\n./gradlew :jib-plugin-extension-common:prepareRelease\n"
  },
  {
    "path": "jib-plugins-extension-common/src/main/java/com/google/cloud/tools/jib/plugins/extension/ExtensionLogger.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.extension;\n\n/** Logger for plugin extensions. */\npublic interface ExtensionLogger {\n\n  /** Levels for log messages generated by plugin extensions, in order of verbosity. */\n  enum LogLevel {\n    ERROR,\n    WARN,\n    LIFECYCLE,\n    INFO,\n    DEBUG\n  }\n\n  void log(LogLevel level, String message);\n}\n"
  },
  {
    "path": "jib-plugins-extension-common/src/main/java/com/google/cloud/tools/jib/plugins/extension/JibPluginExtension.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.extension;\n\n/** Base type for Jib plugin extensions. */\npublic interface JibPluginExtension {}\n"
  },
  {
    "path": "jib-plugins-extension-common/src/main/java/com/google/cloud/tools/jib/plugins/extension/JibPluginExtensionException.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.extension;\n\n/** Exception while running Jib plugin extensions. */\npublic class JibPluginExtensionException extends Exception {\n\n  private final Class<? extends JibPluginExtension> extensionClass;\n\n  /**\n   * Constructs a new exception.\n   *\n   * @param extensionClass plugin extension creating the exception\n   * @param message the detail message\n   */\n  public JibPluginExtensionException(\n      Class<? extends JibPluginExtension> extensionClass, String message) {\n    super(message);\n    this.extensionClass = extensionClass;\n  }\n\n  /**\n   * Constructs a new exception.\n   *\n   * @param extensionClass plugin extension creating the exception\n   * @param message the detail message\n   * @param cause the cause\n   */\n  public JibPluginExtensionException(\n      Class<? extends JibPluginExtension> extensionClass, String message, Throwable cause) {\n    super(message, cause);\n    this.extensionClass = extensionClass;\n  }\n\n  /**\n   * Returns the originating extension class.\n   *\n   * @return originating extension class\n   */\n  public Class<? extends JibPluginExtension> getExtensionClass() {\n    return extensionClass;\n  }\n}\n"
  },
  {
    "path": "jib-plugins-extension-common/src/main/java/com/google/cloud/tools/jib/plugins/extension/NullExtension.java",
    "content": "/*\n * Copyright 2020 Google LLC.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n * use this file except in compliance with the License. You may obtain a copy of\n * the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n * License for the specific language governing permissions and limitations under\n * the License.\n */\n\npackage com.google.cloud.tools.jib.plugins.extension;\n\n/**\n * Represents general notion of a \"null\" (invalid, non-existing, undefined, uninstantiated, or no-op\n * extension) extension.\n */\npublic class NullExtension implements JibPluginExtension {}\n"
  },
  {
    "path": "kokoro/continuous.bat",
    "content": "@echo on\r\n\r\nREM Java 9 does not work with Mockito mockmaker.\r\necho %JAVA_HOME%\r\n\r\ncd github/jib\r\n\r\nREM Stops any left-over containers.\r\nREM FOR /f \"tokens=*\" %%i IN ('docker ps -aq') DO docker rm -vf %%i\r\n\r\nREM Sets the integration testing project.\r\nset JIB_INTEGRATION_TESTING_PROJECT=jib-integration-testing\r\n\r\nREM TODO: Enable integration tests once docker works (b/73345382).\r\ngradlew.bat clean build --info --stacktrace\r\n\r\nexit /b %ERRORLEVEL%\r\n"
  },
  {
    "path": "kokoro/continuous.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o xtrace\n\ngcloud components install docker-credential-gcr\n\n# Docker service does not run by default in Big Sur but can be started with the following commands.\nif [ \"${KOKORO_JOB_CLUSTER}\" = \"MACOS_EXTERNAL\" ]; then\n  source github/jib/kokoro/docker_setup_macos.sh\nfi\n\n# In GCP_UBUNTU_DOCKER, the build script runs in a container and requires additional setup\nif [ \"${KOKORO_JOB_CLUSTER}\" = \"GCP_UBUNTU_DOCKER\" ]; then\n  source github/jib/kokoro/docker_setup_ubuntu.sh\nfi\n\n# docker-credential-gcr uses GOOGLE_APPLICATION_CREDENTIALS as the credentials key file\nexport GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_KEYSTORE_DIR}/72743_jib_integration_testing_key\n\n# Overrides default search order to check credentials in GOOGLE_APPLICATION_CREDENTIALS\ndocker-credential-gcr config --token-source=\"env\"\ndocker-credential-gcr configure-docker\n\n# From default hostname, get id of container to exclude\nCONTAINER_ID=$(hostname)\necho \"$CONTAINER_ID\"\n\n# Stops any left-over containers.\ndocker stop $(docker ps --all --quiet | grep -v \"$CONTAINER_ID\") || true\ndocker kill $(docker ps --all --quiet | grep -v \"$CONTAINER_ID\") || true\n\n# Sets the integration testing project.\nexport JIB_INTEGRATION_TESTING_PROJECT=jib-integration-testing\n\ncd github/jib\n\n./gradlew clean build integrationTest --info --stacktrace"
  },
  {
    "path": "kokoro/docker_setup_macos.sh",
    "content": "#!/bin/bash\n\ndocker-machine ls\ndocker-machine start default\nexport DOCKER_IP=\"$(docker-machine ip default)\"\necho $DOCKER_IP\n\ndocker-machine ssh default \"echo '{ \\\"insecure-registries\\\":[\\\"$DOCKER_IP:5000\\\", \\\"$DOCKER_IP:6000\\\", \\\"$DOCKER_IP:8080\\\"] }' | sudo tee /etc/docker/daemon.json \"\ndocker-machine ssh default \"echo 'DOCKER_OPTS=\\\"--config-file=/etc/docker/daemon.json\\\"' | sudo tee -a /var/lib/boot2docker/profile \"\ndocker-machine ssh default \"mkdir /home/docker/auth; docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > /home/docker/auth/htpasswd\"\ndocker-machine ssh default \"sudo /etc/init.d/docker restart\"\ndocker-machine inspect default\ndocker-machine env default\neval \"$(docker-machine env default)\"\n\nexport JAVA_HOME=\"/Library/Java/JavaVirtualMachines/jdk-8-latest/Contents/Home\"\nexport PATH=$JAVA_HOME/bin:$PATH\necho $JAVA_HOME"
  },
  {
    "path": "kokoro/docker_setup_ubuntu.sh",
    "content": "#!/bin/bash\n\nexport DOCKER_IP_UBUNTU=\"$(/sbin/ip route|awk '/default/ { print $3 }')\"\necho \"DOCKER_IP_UBUNTU: ${DOCKER_IP_UBUNTU}\"\necho \"${DOCKER_IP_UBUNTU} localhost\" >> /etc/hosts\nmkdir -p /tmpfs/auth\ndocker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > /tmpfs/auth/htpasswd"
  },
  {
    "path": "kokoro/presubmit.bat",
    "content": "@echo on\r\n\r\nREM Java 9 does not work with Mockito mockmaker.\r\necho %JAVA_HOME%\r\n\r\ncd github/jib\r\n\r\nREM Stops any left-over containers.\r\nREM FOR /f \"tokens=*\" %%i IN ('docker ps -aq') DO docker rm -vf %%i\r\n\r\ncall gradlew.bat clean build --info --stacktrace\r\n\r\nexit /b %ERRORLEVEL%\r\n"
  },
  {
    "path": "kokoro/presubmit.sh",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o xtrace\n\ngcloud components install docker-credential-gcr\n\n# Docker service does not run by default in Big Sur but can be started with the following commands.\nif [ \"${KOKORO_JOB_CLUSTER}\" = \"MACOS_EXTERNAL\" ]; then\n  source github/jib/kokoro/docker_setup_macos.sh\nfi\n\n# In GCP_UBUNTU_DOCKER, the build script runs in a container and requires additional setup\nif [ \"${KOKORO_JOB_CLUSTER}\" = \"GCP_UBUNTU_DOCKER\" ]; then\n  source github/jib/kokoro/docker_setup_ubuntu.sh\nfi\n\n# From default hostname, get id of container to exclude\nCONTAINER_ID=$(hostname)\necho \"$CONTAINER_ID\"\n\n# Stops any left-over containers.\ndocker stop $(docker ps --all --quiet | grep -v \"$CONTAINER_ID\") || true\ndocker kill $(docker ps --all --quiet | grep -v \"$CONTAINER_ID\") || true\n\ncd github/jib\n\n# we only run integration tests on jib-core for presubmit\n./gradlew clean build :jib-core:integrationTest --info --stacktrace\n"
  },
  {
    "path": "proposals/README.md",
    "content": "# Design Documents and Proposals\n\nThis directory holds the approved design documents and proposals for changes and new features to Jib.\n\n## Contribute\n\n### Submit a Proposal\n\nSubmit a proposal by filing a pull request that adds a `<proposal_name>.md` file to this directory.\n\nFollow the general layout of existing proposals. In general, make sure to include:\n\n- Description of the problem\n- Goals to achieve\n- Proposed solution to the problem\n\nProposals are approved upon merge.\n\n### Review a Proposal\n\nReview pending proposals by commenting on [their pull requests](/../../pulls?q=is%3Aopen+is%3Apr+label%3Aproposal). Proposals in review are labelled with `proposal`.\n\nFor approved proposals, you may open revision pull requests with your suggestions for revision, or provide your comments in a new [issue](/../../issues/new?body=&lt;!--%20Please%20provide%20the%20link%20to%20the%20approved%20proposal%20you%20are%20commenting%20on.%20--&gt;) or messaging the [community](/../../#community). \n"
  },
  {
    "path": "proposals/archives/README.md",
    "content": "# Proposal Archives\n\nThis directory contains proposals that have already been implemented."
  },
  {
    "path": "proposals/archives/build_tarball.md",
    "content": "# Proposal: Build Image Tarball\n\n## Motivation\n\nCurrently, Jib can build a container image to either a registry or a Docker daemon. Recently, there\nhave been requests for the ability to build an image tarball directly to the filesystem so that the\nuser may load it into a Docker daemon via `docker load`, either manually or via a build system.\n\n## Goals\n\n* Build the image and output as a tarball\n* Minimize extra configuration\n\n## Constraints\n\n* The tarball will be saved to the project's build output directory with the name `jib-image.tar`\n\n## Intended Workflow\n\nThe user will be able to output a `docker load`able image tarball by running `gradle jibBuildTar`\nfor Gradle or `mvn jib:buildTar` for Maven.\n\n## Implementation\n\nThe \"build tarball\" task is almost identical to the \"build to docker daemon\" task, except for the\nfinal step, which writes the final tarball to a file instead of piping it to a `docker load`\ncommand. To avoid duplicate code, a boolean parameter can be used in the steps runners to determine\nwhether to build to a Docker daemon or build to a tarball.\n\nThe following changes will be made to the code:\n1. Add a private output path configuration parameter to `BuildConfiguration`\n2. Rename `BuildTarballAndLoadDockerStep` to `LoadDockerStep`\n3. Add a new step `WriteTarFileStep` for writing a Blob to disk\n4. Add `BuildSteps#forBuildToTarball()`, which would contain the same steps as\n`forBuildToDockerDaemon()` up until after the `FinalizingStep`, where it would run a\n`WriteTarFileStep` instead of a `LoadDockerStep`\n5. Add a new task and mojo that would call `BuildSteps.forBuildToTarball()`"
  },
  {
    "path": "proposals/archives/cache_v2.md",
    "content": "# Proposal: Improved cache mechanism\n\nImplemented in: **v0.10.0**\n\nThe tracking issue is at [#637](https://github.com/GoogleContainerTools/jib/issues/637).\n\n## TLDR;\n\nThe current cache design is less-than-ideal and should be revamped before releasing [Jib as a library for Java](https://github.com/GoogleContainerTools/jib/issues/337).\n\n## Current cache mechanism\n\nIn the current state (as of version `0.9.9`), Jib caches layer data in a directory on disk. Layer tarballs are stored as files in that directory, and metadata for all layers are stored in a single JSON file.\n\nFor example, the cache directory layout may look like:\n\n```\n  36a2b7401dcddc50a35aeaa81085718b9d5fbce9d607c55a1d79beec2469f9ac.tar.gz  \n  c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986.tar.gz\n  metadata-v2.json\n```\n\nThe metadata stores a list of metadata regarding each of the layers, including its diff ID (uncompressed digest), last modified time, and layer entries (the layout of the tarball including what files actually went into building the tarball).\n\nOne of the main cache queries to perform during the Jib execution is to check if a layer is up-to-date (recently stored in the cache). Currently, this mechanism works by storing the last modified time of the layer and the layer entries. Jib queries the cache for layers that match some layer entries. If the last modified time of that cached layer precedes the modification times of the files in the layer entries, then that cached layer is used; otherwise, the layer is rebuilt from the newly-modified files.\n\n### Problems\n\nThe problems with the current cache mechanism include:\n- When multiple Jib executions using the same cache run in parallel:\n  - Concurrent writes to the metadata could result in corruption of the metadata file. See [#848](https://github.com/GoogleContainerTools/jib/issues/848).\n  - Only the last-finishing execution will have its metadata updates written to the metadata. The other executions will have their metadata updates lost, resulting in less-than-optimal caching.\n- The up-to-date check is tightly coupled to the implementation of the cache. This does not allow for easy switching of the strategy to use for up-to-date checks.\n- Retrieval of layers by layer entries currently involves checking through the entire metadata and matching against the layer entries stored in the metadata. This could become slow as the cache grows in size.\n- The current implementation tightly couples the actual storage implementation with the contents stored in the cache. This does not allow for easily implementing new storage engines with possibly different underlying storage systems.\n- Since the metadata is only written out at the end of a Jib execution (successful or failure), interrupted Jib executions would result in lost metadata updates.\n\nThese problems should be resolved before [Jib as a library for Java](https://github.com/GoogleContainerTools/jib/issues/337) is released.\n\n## Proposal\n\n### Goals\n\n- Clearly-defined storage engine interface that decouples the query system from the storage engine.\n- A default storage engine implementation that stores layer data independently and can retrieve layers by the layer entries that built that layer in constant time.\n\n### Solution\n\n#### Storage engine interface\n\nThere are 4 actions that would be performed against the storage engine:\n\n- Save a cache entry\n- List the entries in the cache\n- Retrieve a cache entry by layer digest\n- Retrieve a layer digest by a selector (explained below)\n- (Optional) Prune the cache\n\nCache entry writes are provided with:\n\n- Layer blob (generates the layer digest, diff ID, and size)\n- Optional selector to additionally reference the layer\n- Optional metadata blob\n\nCache entry reads are provided with:\n\n- Layer digest\n- Layer diff ID\n- Layer size\n- Layer blob\n- Optional metadata blob\n\n#### Storage engine implementation\n\nThere are two types of layers - application layers and base image layers. Base image layers would only need to store their layer data (layer digest and diff ID). Application layers need to store their layer data along with metadata and a custom selector.\n\nIn the provided storage implementation, the metadata will be just the last modified time of the layer (the latest modification time for the files that go into the layer). **The selector will be a digest of the layer entries that built the layer.**\n\nFor example, the cache directory structure looks like:\n\n```\nlayers/\n  36a2b7401dcddc50a35aeaa81085718b9d5fbce9d607c55a1d79beec2469f9ac/\n    326a609681777ee4ca02b1898579c9e07801ef066a629a3c59fa6df6ab42b7aa\n    metadata\nselectors/\n  65de3b72aaf98e4f300ccdf7d64bf9a3b1e23c8c44a1242265f717db1a0877e9\n```\n\nThe `layers/` directory consists of directories for each layer, named by the layer digest.\nInside each of the layer directories:\n- The layer tarball is named by the layer diff ID\n- The metadata is stored in the file `metadata`\n\nThe `selectors/` directory consists of a file for each selector, named by the selector digest. The selector file contents will be the digest of the layer the selector references. In the example, `selectors/65de3b72aaf98e4f300ccdf7d64bf9a3b1e23c8c44a1242265f717db1a0877e9` will have contents `36a2b7401dcddc50a35aeaa81085718b9d5fbce9d607c55a1d79beec2469f9ac`.\n\nAll writes should lock the file they are writing to.\n\n### Analysis\n\n*Note: This analysis assumes that directory traversals are constant, but some filesystems have linear time directory traversal. To account for directory traversals, see [directory sharding](#directory-sharding) below.*\n\n#### Save a cache entry\n\nEach cache entry (layer data, metadata, selector) is stored independently. This solves all of the concurrent metadata write problems due to a single metadata file. Writes are now *constant time* rather than linear in the size of the cache.\n\n#### List the entries in the cache\n\nListing the entries (by list of layer digests) is a simple call to list the names of the directories in `layers/`. Listing is *linear* in the size of the cache.\n\n#### Retrieve a cache entry by layer digest\n\nRetrieving an entry by layer digest is simply finding the file referenced at `layers/<the layer digest>` (along with associated files). This operation is *constant* in the size of the cache.\n\n#### Retrieve a layer digest by a selector\n\nThe higher-level operation is finding a layer that was built by a list of layer entries (the files that built the layer). The operation generates a digest of the layer entries (the selector) and then the storage engine simply finds the file referenced at `selectors/<the selector digest>` (from which the layer digest is retrieved). This operation is *constant* in the size of the cache (and linear in the number of layer entries).\n\n### Directory sharding\n\nDirectory traversal may be at worst a linear time operation (such as ext2 and FAT), and at best a logarithmic time operation (such as with the B-tree directory structure of ext3/4, HFS+, NTFS). The linear time directory traversal could potentially be an issue if the cache grows without bound. To address this issue, the contents of a directory can be sharded into a set number of bins to achieve logarithmic time traversal. For example, given a flat directory of files:\n\n```\ndirectory/\n  file1\n  file2\n  ...\n  file100000\n```\n\nThe files can be placed in a select number of bins (say 4 in this case):\n\n```\ndirectory/\n  bin1/\n    file1\n    ...\n  bin2/\n    file25001\n    ...\n  bin3/\n    file50001\n    ...\n  bin4/\n    file75001\n    ...\n```\n\nThis technique can be applied recursively within each bin as well (with a sentinel to denote recursive termination).\n\nBy choosing a constant number of bins, the traversal at each directory is constant time. The total depth of the sharding is logarithmic to the number of files. *The overall traversal time is logarithmic.*\n\nIn practice, an effective number of bins may be 256, named by the hex codes `00` through `ff`.\n\n### Locking\n\nAll file writes will obtain an exclusive lock on the file and all file reads will obtain a shared lock on the file. However, obtaining locks on the same file during concurrent executions of Jib within the same JVM would result in an `OverlappingFileLockException`. Therefore, the cache cannot rely on `FileLock`s for concurrent I/O control on files.\n\n#### Save a cache entry\n\nSaving a cache entry requires saving the layer blob and metadata to a layer directory under `layers/` and the selector file to the `selectors/` directory. \n\nThe layer directory should be written atomically to avoid reads that may read an incomplete layer directory.\n\nThe selector file can be written atomically anytime after the layer directory is fully written.\n\nAll atomic writes should be done by first writing to a temporary location (outside of the cache) and then moving to the desired location.\n\n#### List the entries in the cache\n\nListing the entries in the cache just requires obtaining a list of all the layer directories under `layers/`.\n\n#### Retrieve a cache entry by layer digest\n\nSince layer directories are only moved to their intended location after finishing the atomic write, retrieving by layer digest (via finding the file at the intended location) would always retrieve a completely finished and valid layer directory.\n\n#### Retrieve a layer digest by a selector\n\nSince selectors are only written after the layer directory it selects is completely written, retrieving by selector would always retrieve a completely finished and valid layer directory.\n"
  },
  {
    "path": "proposals/archives/docker_build.md",
    "content": "# Proposal: Build to Docker Daemon\n\nImplemented in: **v0.9.0**\n\n## Motivation\n\nCurrently, Jib builds and pushes container images to a Docker registry without the need for a Docker daemon. However, for development use cases where the developer does not have a registry set up but do have a Docker daemon available, they may wish to build to the Docker daemon directly.\n\n## Goals\n\n* Build to local Docker daemon\n* Build to [minikube](https://github.com/kubernetes/minikube) (remote) Docker daemon\n* Minimize extra configuration\n\n## Constraints\n\n* Building to local Docker daemon should be an extra feature, not the default\n\n## Intended Workflow\n\nBuilding to a Docker daemon should be as simple as calling a new task/goal (`jib:buildDocker` for Maven and `jibBuildDocker` for Gradle).\n\n## Implementation\n\nThere are three main parts to implementing this proposal:\n\n1. Build an image tarball stream, with the format (see [tarexport/load.go](https://github.com/moby/moby/blob/master/image/tarexport/load.go) for full details):\n  - Each layer as a GZipped tarball named with its SHA256 digest.\n  - A `config.json` as the container config, containing:\n    - `OS`: `linux`\n    - `RootFS.DiffIDS`: in-order [diff IDs](https://github.com/opencontainers/image-spec/blob/master/config.md#layer-diffid) for all the uncompressed layers\n  - A `manifest.json` containing the following fields:\n    - `Config`: The filename of the container config JSON\n    - `Layers`: in-order list of filenames for each layer\n2. Send the image tarball stream to the Docker daemon using [`docker load`](https://docs.docker.com/engine/reference/commandline/load/) CLI command.\n\nNote that currently, build, cache, and push are all in `BuildImageSteps`. In order to implement parts 2 and 3, these stages would need be modularized so that push can be replaced with build image tarball stream and send to Docker daemon.\n\nIf in the future, we would like to not use the `docker` CLI, we would need to:\n\n1. Find the Docker daemon.\n  - This should use the same Docker daemon that the `docker` CLI would.\n    - The default host is `unix:///var/run/docker.sock` or `tcp://127.0.0.1:2375`.\n  - Remote Docker daemon can be configured with environment variables, such as those output by `minikube docker-env`. These include:\n    - `DOCKER_TLS_VERIFY`\n    - `DOCKER_HOST`\n    - `DOCKER_CERT_PATH`\n    - `DOCKER_API_VERSION`\n2. Send the image tarball stream to the Docker daemon using the [Docker Engine API](https://docs.docker.com/engine/api/v1.37/#operation/ImageLoad). This can be done manually with HTTP requests, or by using a Docker-Java client like [docker-java](https://github.com/docker-java/docker-java).\n"
  },
  {
    "path": "proposals/archives/events.md",
    "content": "# Proposal: Emit events from Jib Core\n\nThe tracking issue is at [#714](https://github.com/GoogleContainerTools/jib/issues/714).\n\n## Motivation\n\nCurrently, Jib logs various log messages via injecting a [`JibLogger`](https://github.com/GoogleContainerTools/jib/blob/02f7f41874223e1e6acf2a40648b5b3695877397/jib-core/src/main/java/com/google/cloud/tools/jib/JibLogger.java) interface into the execution steps in the `builder` package. However, the data is not structured. The user (of Jib Core) needs to parse log messages in order to obtain useful information. The log messages are also catered towards the `jib-maven/gradle-plugin` execution output.\n\n## Solution\n\nEmit events with structured information.\n\n## Goals\n\n- Flexible to support different event payloads\n- Extensible with different event types\n- Minimal overhead if not used\n\n## Proposal\n\nAn `EventHandlers` class holds handlers to pass into the execution steps. This is used by `ExecutionContext`.\n\n```java\nclass EventHandlers {\n  \n  // Handles `E` event class to with `eventConsumer`.\n  <E extends JibEvent> add(JibEventType<E> eventType, Consumer<E> eventConsumer);\n  \n  // Handles all events.\n  add(Consumer<JibEvent> eventConsumer);\n}\n```\n\nEmitted events will be matched to handlers by their exact type. `JibEvent`s should **not** inherit from each other. `JibEventType` defines constants for all the possible event types to add handlers for.\n\nAn example usage could look like this:\n\n```java\n// In Jib Core\nclass PushingBlobEvent implements JibEvent {\n  \n  DescriptorDigest getDigest();\n  URL getUploadLocation();\n}\n\n// Called by user\nExecutionContext executionContext = \n    ExecutionContext.newContext()\n                    .addEventHandler(JibEventType.PUSHING_BLOB, event -> {\n                      // Do some processing on event, like:\n                      System.out.println(\n                          \"Pushing blob \" + event.getDigest() + \" to \" +\n                              event.getUploadLocation());\n                    })\n                    .addEventHandler(allJibEvents -> {\n                      System.out.println(\"Some event happened\");\n                    })\n                    // Some class that has pre-defined handlers that print log messages.\n                    .addEventHandlers(new LoggingEventHandlers(jibLogger));\nJib.from(...)\n   ...\n   .containerize(\n       Containerizers.withExecutionContext(executionContext)\n                     .to(RegistryImage.named(targetImage)));\n```\n"
  },
  {
    "path": "proposals/archives/java_agents_and_more_layers.md",
    "content": "# Proposal: Give users ability to add Java agents, arbitrary files, and separate dependencies\n\nImplemented in: **v0.9.5**\n\n## Motivation\n\nThere are 3 feature requests that may be able to be solved together. These are:\n\n- Give users ability to add Java agents ([#378](/../../issues/378))\n- Give users ability to copy arbitrary files to the image ([#213](/../../issues/213))\n- Separate frequently changing from non-changing dependencies for more incrementality ([#403](/../../issues/403))\n\n## Synopsis of the issues\n\n### Give users ability to add Java agents ([#378](/../../issues/378))\n\nCurrently, Jib only adds application files to the container image. These include dependency JARs, resource files, and classes. However, Java developers often run their applications with Java Agents that execute as part of the JVM invocation.\n\nFor example, in [this Dockerfile example](https://github.com/saturnism/spring-petclinic-gcp/blob/master/docker/Dockerfile), the Cloud Debugger and Cloud Profiler agents are added to the container image. The agent archives are first downloaded, and then extracted to their own directory on the image. The archives contains the `.so` file to pass to the java invocation in the form of:\n\n```shell\njava -agentpath:/path/to/agent/files/someagent.so ...\n```\n\n### Give users ability to copy arbitrary files to the image ([#213](/../../issues/213))\n\nUsers may wish to add other files for use in the image. Currently, the way to do this would be to place the files within the application resources. However, this conflates the classpath of the application, the file can only be reached under the `/app/resources` path, and changes to the file would mean repushing all the resources. Therefore, users should have some way of adding files to a new custom layer.\n\n### Separate frequently changing from non-changing dependencies for more incrementality ([#403](/../../issues/403))\n\nThe dependencies layer tends to be the largest layer in the image, since it contains all the dependency JARs. However, not all dependencies are the same. Some may change more frequently than others (especially `-SNAPSHOT` versions). Therefore, users should have some way to group different dependencies into different layers. For example, separating released artifacts from snapshot artifacts, and grouping dependencies from a BOM into a layer, or separating more frequently changed ones from less frequently changed ones to reduce the amortized push time.\n\n## Proposal\n\nThe proposal is to define a new convention for adding more files to the Jib container.\n\nThe user can add arbitrary files to the image by placing them in a `src/main/jib` directory. This will copy all files within `src/main/jib` to the image's root directory `/`, maintaining the same structure. For example, if the user has a text file at `src/main/jib/dir/hello.txt`, then the built image have `/dir/hello.txt`.\n\nThe directory should also be able to be configured to override the `src/main/jib` default.\n\n## Alternative Proposals\n\n### Rejected Proposal 1\n\n**The alternative proposal was rejected** because we deemed that it required too much extra configuration on the part of the user.\n\nThe proposal is to keep the configuration for adding additional files and Java agents separate from the configuration for separating the application layers into thinner layers. The point of this is to keep the configuration simple and not allow arbitrary user-controlled layering.\n\nThe configuration for adding additional files (including Java agents) would look something like:\n\n*(The exact configuration is to be decided before this PR is merged. Alternative naming for this parameter include: `additionalFiles`, `extraFiles`, and `copyFiles`.)*\n\n*Maven* \n\n```xml\n<addFiles> \n  <addFile>\n    <from>path/to/file</from>\n    <to>/path/on/image</to>\n  </addFile>\n  <addFile>\n    <from>another/file</from>\n    <to>/another/path/on/image</to>\n  </addFile>\n</addFiles>\n```\n\n*Gradle*\n\n```groovy\naddFile 'path/to/file', '/path/on/image'\naddFile 'another/file', '/another/path/on/image'\n```\n\n*The semantics of `from` and `to` will mostly be similar to [Dockerfile `COPY`](https://docs.docker.com/engine/reference/builder/#copy). However, glob matching won't be supported.* \n\nFor separating application layers into thinner layers, the solution will only separate dependencies for simplicity. Jib will *automatically* separate `-SNAPSHOT` dependencies and dependencies with the same group as the project into a separate layer.\n\nIn the future, we may consider allowing the user to configure this in the form of something like what was originally suggested in [#403](/../../issues/403):\n\n```xml\n<volatileDependencies>com.yourcompany.*, *-SNAPSHOT</volatileDependencies>\n```\n\nThis would match dependencies that are in the `com.yourcompany` package and `SNAPSHOT` dependencies and place these in a new volatile-dependencies layer.\n\n### Rejected Proposal 2\n\n**The alternative proposal was rejected** because we deemed that layering should be an implementation detail that should not be exposed to the user.\n\nCurrently (`v0.9.1`), Jib's configuration looks like:\n\n```xml\n<configuration>\n  <from>...</from>\n  <to>...</to>\n  <container>...</container>\n</configuration>\n\n```\n\n- `from` defines the base image and credentials\n- `to` defines the target image and credentials\n- `container` defines container configuration like JVM flags, program arguments, and exposed ports\n\nThe alternative proposal is to add another top-level configuration object called `layers` to add additional layers with custom files. The configuration would look like:\n\n```xml\n<layers>\n  <layer>\n    <files>\n      <file>\n        <from>path/to/file</from>\n        <to>/path/on/image</to>\n      </file>\n    </files>\n    <matchDependencies>org.springframework:*, org.hibernate:*...</matchDependencies>\n    <matchResources>static/, *.jpg</matchResources>\n    <matchClasses>my.package.that.does.not.change.much.*</matchClasses>\n  </layer>\n</layers>\n```\n\nThe user can choose whatever subset of `layer` parameters to define.\n\nParameter | Description\n--- | ---\nfiles | In the form `<src>:<dest>`, where the file `<src>` is added to the image at path `<dest>`.\nmatchDependencies | Matches dependencies with the given patterns and adds those dependency artifacts into this layer rather than the original dependencies layer.\nmatchResources | Like `matchDependencies`, but for resource files.\nmatchClasses | ... but for classes files.\n\nFor implementation, `files` should be implemented first to support Java agent usage and `match*` could be added in later updates.\n\nAnd similarly for Gradle:\n\n```groovy\nlayer {\n  file 'path/to/file', '/path/on/image'\n  matchDependencies = 'org.springframework:*, org.hibernate:*...'\n  matchResources = 'static/, *.jpg'\n  matchClasses = 'my.package.that.does.not.change.much.*'\n}\n```\n\n## Other options considered\n\n### Give users ability to add Java agents ([#378](/../../issues/378))\n\n#### Option 1 - Specific configuration for each agent\n\nProvide agent-specific configuration options similar to [CloudFoundry BuildPacks](https://github.com/cloudfoundry/java-buildpack). This would mean that **each agent would have a different set of configuration parameters** specifically for that agent. Each agent-specific implementation would automatically download and add the agent files to the container image and automatically configure the JVM flags. \n\n**Benefit:** User would be given the exact and minimal controls over specific agents.\n**Downside:** We would have to implement and maintain custom implementations for each agent we wish to support.\n\n#### Option 2 - `agents` configuration\n\nProvide configuration options to add agents by specifying the archive download location or local files. For example, the configuration could look like:\n\n```xml\n<agents>\n  <agent>\n    <location>${project.basedir}/myagent-archive.tar.gz</location>\n    <pathOnImage>/opt/myagent</pathOnImage>\n  </agent>\n</agents>\n<container>\n  <jvmFlags>\n    <jvmFlag>-agentpath:/opt/myagent/myagent.so=-some_option=yes</jvmFlag>\n  </jvmFlags>\n</container>\n```\n\n#### Option 3 - copy to image\n\nSolve this as part of *### Give users ability to copy arbitrary files to the image ([#213](/../../issues/213))*.\n\n### Give users ability to copy arbitrary files to the image ([#213](/../../issues/213))\n\n#### Option 1 - Single custom layer\n\nThe user defines files to copy, along with their destinations on the image. For example:\n\n```xml\n<copies>\n  <copy>\n    <source>path/to/file</source>\n    <pathOnImage>/path/on/image</pathOnImage>\n  </copy>\n</copies>\n```\n\nAll files defined would be placed in a single layer.\n\n**Benefit:** Prevents possible large number of layers.\n**Downside:** All files are in same layer.\n\n#### Option 2 - Multiple custom layers\n\nThe user can define their own custom layers, and define what files to copy into each layer. For example:\n\n```xml\n<additionalLayers>\n  <additionalLayer>\n    <copies>\n      <copy>\n        <source>path/to/file</source>\n        <pathOnImage>/path/on/image</pathOnImage>\n      </copy>\n    </copies>\n  </additionalLayer>\n</additionalLayers>\n```\n\n**Benefit:** Can be used to solve all three issues (with more options for other things to add to such custom layers)\n**Downside:** Quite verbose\n\n#### Option 3 - Multiple automatic layers\n\nThe user defines files to copy like in *Option 1*, but a separate layer is generated for each copy.\n\n#### Option 4 - Multiple custom layers by layer ID\n\nLike in *Option 1*, but the user can set a layer ID for each copy. Copies with the same layer ID are placed in the same layer. For example:\n\n```xml\n<copies>\n  <copy>\n    <source>path/to/file</source>\n    <pathOnImage>/path/on/image</pathOnImage>\n    <layerId>somelayername</layerId>\n  </copy>\n  <copy>\n    <source>path/to/another/file</source>\n    <pathOnImage>/another/path/on/image</pathOnImage>\n    <layerId>somelayername</layerId> ← this goes in the same layer as the first\n  </copy>\n  <copy>\n    <source>path/to/yet/another/file</source>\n    <pathOnImage>/path/on/image</pathOnImage>\n    <layerId>someotherlayername</layerId>\n  </copy>\n</copies>\n```\n\n**Benefit:** Keeps the configuration concise\n\n### Separate frequently changing from non-changing dependencies for more incrementality ([#403](/../../issues/403))\n\n#### Option 1 - Top-level configuration\n\nConfiguration options `silentDependencies` and `mutableDependencies`, as recommended in [#403](/../../issues/403). Example:\n\n```xml\n<silentDependencies>org.springframework:*, org.hibernate:*, *:commons-*, org.webjars:*</silentDependencies>\n<mutableDependencies>com.yourcompany:*,*:*:*-SNAPSHOT</mutableDependencies>\n```\n\n**Benefit:** Simple and understandable\n**Downside:** Does not solve any of the other issues\n\n#### Option 2 - for classes, resources, and dependencies\n\nThe user would be able to define patterns to match against both dependencies and any other files that go in the application layers. These matched files are taken from the `classes`, `resources`, and `dependencies` layers and placed into 3 new layers - `silentClasses`, `silentResources`, and `silentDependencies`. The configuration would look like:\n\n```xml\n<silentDependencies>org.springframework:*, org.hibernate:*...</silentDependencies>\n<silentFiles>static/, *.jpg</silentFiles>\n<silentPackages>my.package.that.does.not.change.much</silentPackages>\n```\n\n**Benefit:** Supports splitting of all application layers into thinner layers.\n**Downside:** Does not solve any of the other issues\n\n#### Option 3 - in custom layers\n\nSimilar to the above *[Option 2 - Multiple custom layers](#option-2---multiple-custom-layers)*:\n\n```xml\n<additionalLayers>\n  <additionalLayer>\n    <copies>...</copies>\n    <matchDependencies>org.springframework:*, org.hibernate:*...</matchDependencies>\n    <matchFiles>static/, *.jpg</matchFiles>\n    <matchPackages>my.package.that.does.not.change.much</matchPackages>\n  </additionalLayer>\n</additionalLayers>\n```\n\nIn the above example, each configuration option for `additionalLayer` is optional.\n\n**Benefits:** Solves all 3 issues\n**Downside:** Might be confusing\n"
  },
  {
    "path": "proposals/archives/jib_core_library.md",
    "content": "# Proposal: Jib Core as a library for Java\n\nThe tracking issue is at [#337](https://github.com/GoogleContainerTools/jib/issues/337).\n\n# Goal\n\nDesign for Jib Core as a Java library for building container images.\n\n# Proposed API\n\n`Jib` - the main entrypoint for using Jib Core\n- `JibContainerBuilder from(String baseImageReference)`\n- `JibContainerBuilder from(ImageReference baseImageReference)`\n- `JibContainerBuilder from(RegistryImage baseImage)`\n\n`JibContainerBuilder` - configures the container to build\n- `JibContainerBuilder addLayer(List<Path> files, Path pathInContainer)`\n- `JibContainerBuilder addLayer(LayerConfiguration)`\n- `JibContainerBuilder setLayers(List<LayerConfiguration>/LayerConfiguration...)`\n\n- `JibContainerBuilder setEntrypoint(List<String>/String...)`\n- `JibContainerBuilder setProgramArguments(List<String>/String...)`\n- `JibContainerBuilder setEnvironment(Map<String, String> environmentMap)`\n- `JibContainerBuilder addEnvironmentVariable(String name, String value)`\n- `JibContainerBuilder setExposedPorts(List<Port>/Port...)`\n- `JibContainerBuilder addExposedPort(Port port)`\n- `JibContainerBuilder setLabels(Map<String, String> labelMap)`\n- `JibContainerBuilder addLabel(String key, String value)`\n- `JibContainerBuilder setFormat(ImageFormat)`\n- `JibContainerBuilder setCreationTime(Instant creationTime)`\n- `JibContainerBuilder setUser(String user)`\n- `JibContainer containerize(Containerizer)`\n\nThree `TargetImage` types (`RegistryImage`, `DockerDaemonImage`, and `TarImage`) define the 3 different targets Jib can build to.\n\n`RegistryImage` - builds to a container registry\n- `static RegistryImage named(ImageReference/String)`\n- `RegistryImage addCredential(String username, String password)`\n- `RegistryImage addCredentialRetriever(CredentialRetriever)`\n\n`DockerDaemonImage` - builds to a Docker daemon\n- `static DockerDaemonImage named(ImageReference/String)`\n- `DockerDaemonImage setDockerExecutable(Path)`\n\n`TarImage` - builds to a tarball archive\n- `Builder`\n  - `TarImage saveTo(Path outputFile)`\n- `static Builder named(ImageReference/String)`\n\n`Containerizer` - configures how and where to containerize to\n- `static Containerizer to(RegistryImage)`\n- `static Containerizer to(DockerDaemonImage)`\n- `static Containerizer to(TarImage)`\n- `Containerizer withAdditionalTag(String tag)`\n- `Containerizer setExecutorService(ExecutorService)`\n- `Containerizer setCacheConfiguration(CacheConfiguration)`\n- `Containerizer setEventHandlers(EventHandlers)`\n- `Containerizer setAllowInsecureRegistries(boolean)`\n- `Containerizer setToolName(String)`\n\n## For Java containers\n\n`JavaContainerBuilder` - builds a `JibContainerBuilder` for Java apps\n- `static JavaContainerBuilder builder()`\n- `JavaContainerBuilder addDependencies(List<Path> dependencyFiles)`\n- `JavaContainerBuilder addResources(List<Path> resourceFiles)`\n- `JavaContainerBuilder addClasses(List<Path> classFiles)`\n- `JavaContainerBuilder addToClasspath(List<Path> otherFiles)`\n- `JavaContainerBuilder setJvmFlags(List<String>/String... jvmFlags)`\n- `JavaContainerBuilder setMainClass(String mainClass)`\n- `JavaContainerBuilder toContainerBuilder()`\n\n# Simple example\n\n```java\nJib.from(\"busybox\")\n   .addLayer(Arrays.asList(Paths.get(\"helloworld.sh\")), \"/helloworld.sh\")\n   .setEntrypoint(\"/helloworld.sh\")\n   .containerize(\n       Jib.to(RegistryImage.named(\"coollog/jibtestimage\")\n                           .setCredential(\"coollog\", \"notmyrealpassword\"));\n```\n"
  },
  {
    "path": "proposals/archives/log_output_v2.md",
    "content": "# Proposal: Improve Jib Log Output\n\nImplemented in: **v0.9.0**\n\n## Motivation\n\nTo have a more organized, readable, and aethetically-pleasing log output.\n\n## Goals\n\n- Consistency between Maven and Gradle\n- Organize across levels of verbosity\n\n## Proposed Design\n\n### Categories\n\nThe log output will be organized into 4 categories:\n\n#### Lifecycle\n\nMessages that are part of the execution and will always show up.\n\n#### Info\n\nMessages that are extra information and do not need to be displayed by default. Note that for Maven, these will be at the same level as Debug.\n\n#### Debug\n\nMessages that are useful for debugging what went wrong, but not as useful (or convoluting) as user-facing information. These would be helpful to include in, for example, a bug report.\n\n#### Warn/Error\n\nThese messages will be displayed by default, but as the warning/error format of the specific Maven/Gradle logger implementations. Jib should not explicitly log error messages, but rather throw those messages as part of exceptions.\n\n### Information to include\n\nThe information to include, in the order in which they should appear are:\n\n1. (info) Where inputs are inferred from, including\n    1. The main class\n1. (info) Files that went into each layer\n    1. Classes, resources, dependencies\n1. (lifecycle/info) Important build steps\n    1. Show what’s skipped?\n    1. Asynchronous steps (the order may vary between runs)\n        - [lifecycle] Retrieving credentials for \\<base image>...\n            - [info] Credentials for \\<target image> found via (docker-credential-*/Docker config/Maven settings) in \\<### ms>\n        - [lifecycle] Retrieving credentials for \\<target image>...\n            - [info] Credentials for \\<target image> found via (docker-credential-*/Docker config/Maven settings) in \\<### ms>\n        - [lifecycle] Getting base image \\<base image>…\n            - [info] Fetching manifest for \\<base image>...\n            - [info] Fetched manifest for \\<base image> in \\<### ms>\n            - [info] Fetching layer \\<digest> of \\<base image>...\n            - [info] Fetched layer \\<digest> of \\<base image> in \\<### ms>\n            - [info] Pulled base image in \\<### ms>\n        - [lifecycle] Building dependencies layer...\n            - [info] Built and cached dependencies layer \\<digest> in \\<### ms>\n            - [info] Pushing dependencies layer \\<digest>...\n            - [info] Pushed dependencies layer \\<digest> in \\<### ms>\n        - [lifecycle] Building resources layer\n            - Same as for dependencies\n        - [lifecycle] Building classes layer\n            - Same as for dependencies\n        - [lifecycle] Finalizing…\n            - [info] Pushing container configuration \\<digest>...\n            - [info] Pushed container configuration \\<digest> in \\<### ms>\n            - [info] Pushing manifest...\n            - [info] Pushed manifest in \\<### ms>\n        - [lifecycle] Build complete in \\<### s>\n1. Finalized information\n    1. Container entrypoint set to \\<entrypoint>\n    1. Built and pushed image as \\<target image>\n\n### Example log\n\nAt lifecycle level:\n\n```\n[lifecycle] Containerizing application to gcr.io/my-gcp-project/my-app...\n[lifecycle]\n[lifecycle] Retrieving credentials for gcr.io/distroless/java…\n[lifecycle] Retrieving credentials for gcr.io/my-gcp-project/my-app...\n[lifecycle] Getting base image gcr.io/distroless/java…\n[lifecycle] Building dependencies layer...\n[lifecycle] Building resources layer...\n[lifecycle] Building classes layer...\n[lifecycle] Finalizing…\n[lifecycle] Build complete in 2.1354s\n[lifecycle]\n[lifecycle] Container entrypoint set to [java, -cp, /app/libs/*:/app/resources/:/app/classes/, my.App]\n[lifecycle]\n[lifecycle] Built and pushed image as gcr.io/my-gcp-project/my-ap\n```\n\nAt info level:\n\n```\n[lifecycle] Containerizing application to gcr.io/my-gcp-project/my-app...\n[info] Using main class from maven-jar-plugin/: my.App\n[info] Containerizing application with the following files:\n[info]     Classes:\n[info]         <the files>\n[info]     Resources:\n[info]         <the files>\n[info]     Dependencies:\n[info]         <the files>\n[lifecycle]\n[lifecycle] Retrieving credentials for gcr.io/distroless/java…\n[info] Credentials for gcr.io/distroless/java found via docker-credential-gcr in 400ms\n[lifecycle] Retrieving credentials for gcr.io/my-gcp-project/my-app...\n[info] Credentials for gcr.io/my-gcp-project/my-app found via docker-credential-gcr in 300ms\n[lifecycle] Getting base image gcr.io/distroless/java…\n[info] Fetching manifest for gcr.io/distroless/java...\n[info] Fetched manifest for gcr.io/distroless/java in 300ms\n[info] Fetching layer sha256:fafafafa... of gcr.io/distroless/java…\n[info] Fetching layer sha256:fafafafa... of gcr.io/distroless/java...\n[info] Fetched layer sha256:fafafafa... of gcr.io/distroless/java in 200ms\n[info] Fetched layer sha256:fafafafa... of gcr.io/distroless/java in 200ms\n[info] Pulled base image in 550ms\n[lifecycle] Building dependencies layer...\n[info] Built and cached dependencies layer sha256:ababab... in 20ms\n[info] Pushing dependencies layer sha256:ababab...\n[info] Pushed dependencies layer sha256:ababab... in 200ms\n[lifecycle] Building resources layer\n[info] Built and cached resources layer sha256:bcbcbc... in 20ms\n[info] Pushing resources layer sha256:bcbcbc...\n[info] Pushed resources layer sha256:bcbcbc... in 250ms\n[lifecycle] Building classes layer\n[info] Built and cached classes layer sha256:cdcdcd... in 10ms\n[info] Pushing classes layer sha256:cdcdcd...\n[info] Pushed classes layer sha256:cdcdcd... in 10ms\n[lifecycle] Finalizing…\n[info] Pushing container configuration sha256:cdcdcd...\n[info] Pushed container configuration sha256:cdcdcd... in 100ms\n[info] Pushing manifest...\n[info] Pushed manifest in 150ms\n[lifecycle] Build complete in 2.1354s\n[lifecycle]\n[lifecycle] Container entrypoint set to [java, -cp, /app/libs/*:/app/resources/:/app/classes/, my.App]\n[lifecycle]\n[lifecycle] Built and pushed image as gcr.io/my-gcp-project/my-app\n```\n\n## Appendix: Current output\n\nExample of current Maven output (`mvn compile jib:build`):\n\n```\n[INFO] --- jib-maven-plugin:0.1.7-SNAPSHOT:build (default-cli) @ hello-world ---\n[INFO] Using main class from maven-jar-plugin: com.test.HelloWorld\n[INFO]\n[INFO] Containerizing application with the following files:\n[INFO]\n[INFO] \tDependencies:\n[INFO]\n[INFO] \t\t/google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/libs/dependency-1.0.0.jar\n[INFO] \t\t/usr/local/google/home/qingyangc/.m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar\n[INFO] \t\t/usr/local/google/home/qingyangc/.m2/repository/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar\n[INFO] \t\t/usr/local/google/home/qingyangc/.m2/repository/com/google/guava/guava/23.6-jre/guava-23.6-jre.jar\n[INFO] \t\t/usr/local/google/home/qingyangc/.m2/repository/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar\n[INFO] \t\t/usr/local/google/home/qingyangc/.m2/repository/org/checkerframework/checker-compat-qual/2.0.0/checker-compat-qual-2.0.0.jar\n[INFO] \t\t/usr/local/google/home/qingyangc/.m2/repository/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar\n[INFO] \tResources:\n[INFO]\n[INFO] \t\t/google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/target/classes/20m.2\n[INFO] \t\t/google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/target/classes/aklwejflkjawelkfjlkefjlkawejflaweflkwenfkneawklfewnlknaewlgjnlaewkgjklaewgnjlakewnglaekwnglaewngkwengnglkweangklawnglekangwklgnelkngwlknglekwnglkanglkwgnew\n[INFO] \t\t/google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/target/classes/world2\n[INFO] \tClasses:\n[INFO]\n[INFO] \t\t/google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/target/classes/com\n[INFO]\n[INFO]\n[INFO] Pushing image as gcr.io/qingyangc-sandbox/jibtestimage:built-with-jib\n[INFO]\n[INFO] RUNNING\tBuilding and pushing image\n[INFO] RUNNING\tRetrieving registry credentials for gcr.io\n[INFO] RUNNING\tRetrieving registry credentials for gcr.io\n[INFO] Checking credentials from docker-credential-gcr\n[INFO] Checking credentials from docker-credential-gcr\n[INFO] RUNNING\tBuilding application layers\n[INFO] RUNNING\tBuilding dependencies layer\n[INFO] RUNNING\tBuilding resources layer\n[INFO] Building application layers : 0.931 ms\n[INFO] RUNNING\tBuilding classes layer\n[INFO] RUNNING\tSetting up to push layers\n[INFO] Setting up to push layers : 1.083 ms\n[INFO] Building dependencies layer : 2.82 ms\n[INFO] Building resources layer : 2.879 ms\n[INFO] Building classes layer : 10.285 ms\n[INFO] Using docker-credential-gcr for gcr.io\n[INFO] Using docker-credential-gcr for gcr.io\n[INFO] Retrieving registry credentials for gcr.io : 325.95 ms\n[INFO] Retrieving registry credentials for gcr.io : 325.974 ms\n[INFO] RUNNING\tAuthenticating with push to gcr.io\n[INFO] RUNNING\tAuthenticating pull from gcr.io\n[INFO] Authenticating pull from gcr.io : 674.983 ms\n[INFO] RUNNING\tPulling base image manifest\n[INFO] Authenticating with push to gcr.io : 745.519 ms\n[INFO] RUNNING\tPushing BLOB sha256:c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986\n[INFO] RUNNING\tPushing BLOB sha256:9a6afec7bf36218264ed61dc525d8fe6b585dd7a84b780a327c3663de4ef51b1\n[INFO] RUNNING\tPushing BLOB sha256:3b6efb8e94b25098a394fa4d2e988cc12a6afd7a2b112a2c97cc9899d446ae18\n[INFO] BLOB : sha256:9a6afec7bf36218264ed61dc525d8fe6b585dd7a84b780a327c3663de4ef51b1 already exists on registry\n[INFO] Pushing BLOB sha256:9a6afec7bf36218264ed61dc525d8fe6b585dd7a84b780a327c3663de4ef51b1 : 176.432 ms\n[INFO] BLOB : sha256:c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986 already exists on registry\n[INFO] Pushing BLOB sha256:c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986 : 189.759 ms\n[INFO] BLOB : sha256:3b6efb8e94b25098a394fa4d2e988cc12a6afd7a2b112a2c97cc9899d446ae18 already exists on registry\n[INFO] Pushing BLOB sha256:3b6efb8e94b25098a394fa4d2e988cc12a6afd7a2b112a2c97cc9899d446ae18 : 281.987 ms\n[INFO] Pulling base image manifest : 439.962 ms\n[INFO] RUNNING\tSetting up base image caching\n[INFO] Setting up base image caching : 1.027 ms\n[INFO] RUNNING\tPulling base image layer sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5\n[INFO] RUNNING\tSetting up to push layers\n[INFO] RUNNING\tPulling base image layer sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24\n[INFO] Pulling base image layer sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5 : 0.078 ms\n[INFO] RUNNING\tPulling base image layer sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca\n[INFO] RUNNING\tPushing BLOB sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5\n[INFO] Pulling base image layer sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca : 0.167 ms\n[INFO] Setting up to push layers : 0.1 ms\n[INFO] RUNNING\tPushing BLOB sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca\n[INFO] Pulling base image layer sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24 : 0.281 ms\n[INFO] RUNNING\tPushing BLOB sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24\n[INFO] RUNNING\tBuilding container configuration\n[INFO] Building container configuration : 15.243 ms\n[INFO] BLOB : sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca already exists on registry\n[INFO] Pushing BLOB sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca : 90.115 ms\n[INFO] BLOB : sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5 already exists on registry\n[INFO] Pushing BLOB sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5 : 123.695 ms\n[INFO] Pushing container configuration sha256:71529d335fd983477a62eaf9e1cd37e488c48eb1d9477e09fbd63e37857b251c : 152.741 ms\n[INFO] BLOB : sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24 already exists on registry\n[INFO] Pushing BLOB sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24 : 304.932 ms\n[INFO] RUNNING\tPushing new image\n[INFO] Pushing new image : 1191.479 ms\n[INFO] Building and pushing image : 3149.412 ms\n[INFO]\n[INFO] Container entrypoint set to [java, -cp, /app/libs/*:/app/resources/:/app/classes/, com.test.HelloWorld]\n[INFO]\n[INFO] Built and pushed image as gcr.io/qingyangc-sandbox/jibtestimage:built-with-jib\n```\n\nCurrent Gradle output (`gradle build jib --info`):\n\n```\n:jib (Thread[Task worker for ':',5,main]) started.\nRUNNING Retrieving registry credentials for gcr.io\nUsing jib extension for gcr.io\nRUNNING Retrieving registry credentials for gcr.io\nRetrieving registry credentials for gcr.io : 0.176 ms\nRUNNING Building dependencies layer\nUsing jib extension for gcr.io\nRUNNING Building resources layer\nRUNNING Building classes layer\nRUNNING Authenticating with push to gcr.io\nRetrieving registry credentials for gcr.io : 0.342 ms\nBuilding dependencies layer : 0.351 ms\nBuilding resources layer : 0.43 ms\nRUNNING Authenticating pull from gcr.io\nBuilding classes layer : 0.958 ms\nAuthentication error: Unable to respond to any of these challenges: {bearer=WWW-Authenticate: Bearer realm=\"https://gcr.io/v2/token\",service=\"gcr.io\"}\nAuthentication error: Unable to respond to any of these challenges: {bearer=WWW-Authenticate: Bearer realm=\"https://gcr.io/v2/token\",service=\"gcr.io\"}\nAuthenticating pull from gcr.io : 196.848 ms\nRUNNING Pulling base image manifest\nAuthenticating with push to gcr.io : 237.681 ms\nRUNNING Pushing BLOB sha256:3b6efb8e94b25098a394fa4d2e988cc12a6afd7a2b112a2c97cc9899d446ae18\nRUNNING Pushing BLOB sha256:c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986\nRUNNING Pushing BLOB sha256:9a6afec7bf36218264ed61dc525d8fe6b585dd7a84b780a327c3663de4ef51b1\nBLOB : sha256:3b6efb8e94b25098a394fa4d2e988cc12a6afd7a2b112a2c97cc9899d446ae18 already exists on registry\nPushing BLOB sha256:3b6efb8e94b25098a394fa4d2e988cc12a6afd7a2b112a2c97cc9899d446ae18 : 101.4 ms\nBLOB : sha256:9a6afec7bf36218264ed61dc525d8fe6b585dd7a84b780a327c3663de4ef51b1 already exists on registry\nPushing BLOB sha256:9a6afec7bf36218264ed61dc525d8fe6b585dd7a84b780a327c3663de4ef51b1 : 120.31 ms\nBLOB : sha256:c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986 already exists on registry\nPushing BLOB sha256:c63484398b097b7e9693ac373ac95630bb8d8ad8ff90a3277e7105bb77e8e986 : 137.118 ms\nPulling base image manifest : 361.452 ms\nRUNNING Setting up base image caching\nRUNNING Pulling base image layer sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca\nSetting up base image caching : 0.224 ms\nPulling base image layer sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca : 0.086 ms\nRUNNING Pulling base image layer sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5\nRUNNING Pulling base image layer sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24\nPulling base image layer sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5 : 0.224 ms\nRUNNING Setting up to push layers\nPulling base image layer sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24 : 0.334 ms\nRUNNING Pushing BLOB sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca\nRUNNING Building container configuration\nSetting up to push layers : 0.365 ms\nRUNNING Pushing BLOB sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24\nRUNNING Pushing BLOB sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5\nBuilding container configuration : 1.208 ms\nBLOB : sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca already exists on registry\nPushing BLOB sha256:eb05f3dbdb543cc610527248690575bacbbcebabe6ecf665b189cf18b541e3ca : 89.462 ms\nBLOB : sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5 already exists on registry\nPushing BLOB sha256:15705ab016593987662839b40f5a22fd1032996c90808d4a1371eb46974017d5 : 120.538 ms\nPushing container configuration sha256:71529d335fd983477a62eaf9e1cd37e488c48eb1d9477e09fbd63e37857b251c : 121.327 ms\nBLOB : sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24 already exists on registry\nPushing BLOB sha256:ba7c544469e514f1a9a4dec59ab640540d50992b288adbb34a1a63c45bf19a24 : 149.226 ms\nRUNNING Pushing new image\nPushing new image : 730.65 ms\n\n> Task :jib\nTask ':jib' is not up-to-date because:\n  Task has not declared any outputs.\n\nContainerizing application with the following files:\n\n        Dependencies:\n\n                /google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/libs/dependency-1.0.0.jar\n                /usr/local/google/home/qingyangc/.m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar\n                /usr/local/google/home/qingyangc/.m2/repository/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar\n                /usr/local/google/home/qingyangc/.m2/repository/com/google/guava/guava/23.6-jre/guava-23.6-jre.jar\n                /usr/local/google/home/qingyangc/.m2/repository/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar\n                /usr/local/google/home/qingyangc/.m2/repository/org/checkerframework/checker-compat-qual/2.0.0/checker-compat-qual-2.0.0.jar\n                /usr/local/google/home/qingyangc/.m2/repository/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar\n        Resources:\n\n                /google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/build/resources/main/20m.2\n                /google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/build/resources/main/aklwejflkjawelkfjlkefjlkawejflaweflkwenfkneawklfewnlknaewlgjnlaewkgjklaewgnjlakewnglaekwnglaewngkwengnglkweangklawnglekangwklgnelkngwlknglekwnglkanglkwgnew\n                /google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/build/resources/main/world2\n        Classes:\n\n                /google/src/cloud/qingyangc/sandbox/google3/experimental/users/qingyangc/sandbox/helloproject/build/classes/java/main/com\n\nPushing image as gcr.io/qingyangc-sandbox/jibgradleimage:67923843946446\n\n\nRUNNING Building and pushing image\nRUNNING Building application layers\nBuilding application layers : 0.382 ms\nRUNNING Setting up to push layers\nSetting up to push layers : 0.327 ms\nBuilding and pushing image : 1446.819 ms\n\nContainer entrypoint set to [java, -cp, /app/libs/*:/app/resources/:/app/classes/, com.test.HelloWorld]\n\nBuilt and pushed image as gcr.io/qingyangc-sandbox/jibgradleimage:67923843946446\n\n\n:jib (Thread[Task worker for ':',5,main]) completed. Took 1.468 secs.\n```\n"
  },
  {
    "path": "proposals/archives/maven_configuration_v2.md",
    "content": "# Proposal: Align `jib-maven-plugin` configuration with `jib-gradle-plugin`\n\nImplemented in: **v0.9.0**\n\n## Motivation\n\n`jib-gradle-plugin` introduced a different configuration schema from `jib-maven-plugin`. This schema has many benefits over the original `jib-maven-plugin` configuration. Therefore, to maintain consistency between the two plugins, the `jib-maven-plugin` configuration should be updated to be more similar to the `jib-gradle-plugin` configuration.\n\n## Goals\n\n- Have `jib-maven-plugin` configuration be as similar to `jib-gradle-plugin` configuration as possible\n\n## Non-Goals\n\n- Maintain compatibility with older versions of `jib-maven-plugin` (we are still in alpha)\n\n## Current Configuration Schema\n\nThe current `jib-maven-plugin` configuration looks like:\n\n```xml\n<configuration>\n  <from>openjdk:alpine</from>\n  <registry>localhost:5000</registry>\n  <repository>my-image</repository>\n  <tag>built-with-jib</tag>\n  <credHelpers>\n    <credHelper>osxkeychain</credHelper>\n  </credHelpers>\n  <jvmFlags>\n    <jvmFlag>-Xms512m</jvmFlag>\n    <jvmFlag>-Xdebug</jvmFlag>\n    <jvmFlag>-Xmy:flag=jib-rules</jvmFlag>\n  </jvmFlags>\n  <mainClass>mypackage.MyApp</mainClass>\n  <enableReproducibleBuilds>true</enableReproducibleBuilds>\n  <imageFormat>OCI</imageFormat>\n</configuration>\n```\n\nAnd the current `jib-gradle-plugin` configuration looks like:\n\n```groovy\njib {\n  from {\n    image = 'openjdk:alpine'\n  }\n  to {\n    image = 'localhost:5000/my-image/built-with-jib'\n    credHelper = 'osxkeychain'\n  }\n  jvmFlags = ['-Xms512m', '-Xdebug', '-Xmy:flag=jib-rules']\n  mainClass = 'mypackage.MyApp'\n  reproducible = true\n  format = 'OCI'\n}\n```\n\n## Desired Configuration Schema\n\nTherefore, the desired `jib-maven-plugin` configuration should look like:\n\n```xml\n<configuration>\n  <from>\n    <image>openjdk:alpine</image>\n  </from>\n  <to>\n    <image>gcr.io/my-gcp-project/my-app</image>\n    <credHelper>gcr</credHelper>\n  </to>\n  <jvmFlags>\n    <jvmFlag>-Xmy:flag=jib-rules</jvmFlag>\n  </jvmFlags>\n  <mainClass>mypackage.MyApp</mainClass>\n  <reproducible>true</reproducible>\n  <format>OCI</format>\n</configuration>\n```\n\nThe key changes are:\n\n- Removes `registry`, `repository`, and `tag` - these are now defined as one field `to.image`.\n- Adds an image configuration object that takes:\n  - `to` - the image reference, and\n  - `credHelper` - the credential helper for that image\n- The image configuration object configures both the base image (`from`) and the target image (`to`).\n- `enableReproducibleBuilds` is renamed to `reproducible`.\n- `imageFormat` is renamed to `format`.\n\n## Implementation Details\n\n1. Change `BuildConfiguration` to individual `credHelper`s for the base and target images, not as a list.\n2. Change the `BuildImageMojo` (and `DockerContextMojo`) schema.\n  - The only required field now is `to.image`. "
  },
  {
    "path": "proposals/archives/output_files.md",
    "content": "# Proposal: Image ID, Digest, and Tar File Location Configuration\n\nRelevant issues: [#1561](https://github.com/GoogleContainerTools/jib/issues/1561), [#1921](https://github.com/GoogleContainerTools/jib/pull/1921)\n\n## Motivation\n\nCurrently Jib hardcodes the location of the files it generates during the build, such as the image ID, digest,\nand tar file (for tar builds). For some users, it would be useful to allow configuring the location of these\noutput files in a way that best fits their workflows.\n\n## Current Configuration\n\nJib currently doesn't allow configuring these locations, and instead it uses hardcoded defaults:\n\n- Image tar -> `${buildDir}/jib-image.tar`\n- Image ID -> `${buildDir}/jib-image.id`\n- Image digest -> `${buildDir}/jib-image.digest`\n\n## Proposed Configuration\n\nThe proposal is to allow users to configure their build with the following rules:\n1. Extensions to the filename (like id, digest, tar) will not be automatically appended.\n1. Existing files at the specified locations will be overwritten if necessary.\n1. Running `clean` will not delete output files created outside of the project's build directory.\n1. The configuration will accept both absolute and relative paths. Relative paths are resolved in the build tool's default manner.\n\n#### Maven (`pom.xml`)\n```xml\n<configuration>\n  <outputPaths>\n    <tar>/absolute/location.tar</tar>\n    <digest>relative/path/digest</digest>\n    <imageId>${project.build.directory}/id</imageId>\n  </outputPaths>\n</configuration>\n```\n\n#### Gradle (`build.gradle`)\n```groovy\njib {\n  outputPaths {\n    tar = \"/absolute/location.tar\"\n    digest = file(\"relative/path/digest\")\n    imageId = file(\"$buildDir/id\")\n  }\n}\n```\n\nCorresponding system properties will also be added so the outputs can be set via commandline:\n* `-Djib.outputPaths.tar`\n* `-Djib.outputPaths.digest`\n* `-Djib.outputPaths.imageId`"
  },
  {
    "path": "proposals/archives/progress_output.md",
    "content": "# Proposal: Provide feedback on push progress\n\nRelevant issues: [#806](https://github.com/GoogleContainerTools/jib/issues/806), [#1251](https://github.com/GoogleContainerTools/jib/issues/1251)\n\n## Motivation\n\nCurrently, Jib lacks any progress feedback for layer pushes. In cases where layers take a long time to push, this lack of progress indication:\n\n- makes it hard to estimate how long the build will take\n- makes it unclear as to whether the build is stuck or will complete in some reasonable amount of time\n- may deter first-time users from continuing to use Jib \n\nThis is especially prevalent for first-time users whose first builds would need to push all the layers, including the large base image layers.\n\n## Current output\n\nAn example of the current output (as of version `0.10.0`) looks like:\n\n```\nContainerizing application to <image>...\nwarning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible\nRetrieving registry credentials for gcr.io...\nGetting base image gcr.io/distroless/java...\nBuilding dependencies layer...\nBuilding resources layer...\nBuilding classes layer...\n\nContainer entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, <main class>]\nFinalizing...\n\nBuilt and pushed image as <image>\n```\n\nNote that the build would pause on `Finalizing...` until all layers are finished pushing.\n\n## Goal\n\nTo display fine-grained progress feedback during the build, but not clutter the log messages.\n\n## Design considerations\n\nSince Jib builds/pulls and pushes each layer independently, synchronized progress feedback (completion over all layers, such as a progress bar) could potentially limit the concurrency of the build process - but this might be negligible.\n\nThe available information for each layer include:\n\n- type (classes, resources, dependencies, base image layer, etc.)\n- digest\n- size\n\nThe non-available information for each layer include:\n\n- sizes of other layers (eg. other layers may not have finished building)\n- number of other layers (eg. base image layers have not been resolved before starting to push an application layer)\n\nTherefore, the actions we can perform for progress feedback include:\n\n- monitor the number of bytes sent (and percentage completion)\n\nAnd the actions we cannot perform include:\n\n- know ahead of time how many layers will be pulled/pushed\n\n## Proposal\n\nDisplay an overall progress bar along with the tasks currently being executed.\n\n### Example\n\n```\n[=====================            ] 60% complete\n> Pushing classes layer\n> Pulling base image layer 50501d3b88f7\n> Pushing dependencies layer\n> Pushing base image layer 8b106a18283f\n```\n\nThe currently executing tasks are displayed below the overall progress bar.\n\n*Note that this would replace the logs messages that are currently outputted as those may corrupt the progress bar display.*\n\n### Summary\n\nThe implementation consists of two parts:\n\n1. Emit progress events from the builder steps.\n1. Monitor progress events and display to console.\n\n### Emit progress events\n\nThere are a few issues to address for the progress event design:\n\n- the total amount of work is not known beforehand, so there is no static max progress known at any time\n- the builder steps are asynchronous and therefore progress events should not share a single progress state\n- rather, the progress event receiver should be the only potentially stateful entity\n\nThese issues can be resolved with a *decentralized allocation tree*.\n\n#### Decentralized allocation tree (DAT)\n\nEach node in the DAT is immutable and initialized with a count of allocation units. These allocation units are to be claimed by progress made on that node or child nodes. Each child node claims 1 allocation unit of the parent node, meaning that completion of all allocation units on that child node means completion of 1 allocation unit on the parent node.\n\n##### Example\n\nConsider a root node (`Node A`) with 10 allocation units. A child node (`Node B`) is added with 4 allocation units. Each of these allocation units represents `10% x 25% = 2.5%` of the entire DAT progress. Completion of all the 4 child node allocation units means that the root node completed 1 allocation unit, or `10%`.\n\nNew allocations can be added on-the-fly in a decentralized manner. Let's say a new download started for `123000` bytes and that download should be represented by one of the allocation units of `Node B`. We would create a new node (`Node C`) with `123000` allocation units and have its parent be `Node B`. This establishes the `123000` bytes that would represent `2.5%` of the overall progress without needing to modify other nodes or synchronize with other tasks that may add their own sub-allocations. This also limits the creation of suballocations to only children tasks of the task that created that allocation, making it fully-compatible with a DAG task pipeline.\n\n#### Benefits of DAT approach\n\nThe DAT approach solves all of the issues present:\n\n- The progress % would never decrease even in an unknown total work scenario\n- The dynamic suballocation supports the asynchronous builder steps\n- The allocations are immutable, and state (total progress amount) can be kept at the receiver (DAT reader) side\n\nThe downside is that the overall progress is not linear in scale among the work of each allocation. For example, an allocation representing a download may have each byte represent `1%` of the total progress while another may have each byte represent `2%`.\n\n### Monitor progress events\n\nProgress events will be emitted with\n1. an allocation node, and\n1. a number of progress units completed on that allocation node\n\nTherefore, the progress monitor receiving the progress events will only need to keep a single total progress amount as its state. Upon receiving a progress event, the progress monitor updates its total progress amount by:\n\n```\ntotalProgressAmount += progressUnits / progressTotal * allocationFraction;\n```\n\n`progressUnits` - the progress units from the progress event \\\n`progressTotal` - the number of allocation units for the allocation node associated with the progress event \\\n`allocationFraction` - the fraction of the total progress represented by the allocation node, calculated as the *inverted product of all the parent allocation units*\n\n### Display to console\n\nThe progress monitor can display a progress bar to the console based on the `totalProgressAmount` upon each update. Using `\\r` could work, but results in the console cursor overlapping with the progress bar line. The proposal is to, for each update, move the cursor up a line (`\\033[1A`) and print the progress bar line with a newline at the end.\n\nThe progress monitor can also keep track of which allocation nodes have completed to display the currently executing tasks.\n\nFor Gradle, in order to keep the log messages in sync with Gradle's own log messages (such as the Gradle progress bar), the progress bar display should effectively become a \"footer\" to the normal log messages Jib outputs and be displayed via the same Gradle Logger interface as the rest of the log messages.\n\n## Alternative rejected proposal\n\n*This alternative proposal was rejected because it cluttered the log messages too much.*\n\nDisplay percentage completion of layer pushes at 10s intervals with a 20s cliff.\n\n### Example\n\n```\n...\nGetting base image gcr.io/distroless/java…\nBuilding dependencies layer…\nBuilding resources layer...\nBuilding classes layer...\nPushing base image layer d4c593cddccd: 30%\nPushing dependencies layer: 30%\nPushing base image layer 8b106a18283f: 10%\nPushing resources layer: 100%\nPushing classes layer: 100%\nPushing base image layer 8b106a18283f: 45%\nPushing base image layer 50501d3b88f7: 60%\nPushing base image layer d4c593cddccd: 100%\nPushing dependencies layer: 65%\nPushing base image layer 50501d3b88f7: 100%\nPushing dependencies layer: 90%\nPushing base image layer 8b106a18283f: 70%\nPushing dependencies layer: 100%\nPushing base image layer 8b106a18283f: 90%\nPushing base image layer 8b106a18283f: 100%\n\nContainer entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, <main class>]\nFinalizing...\n\nBuilt and pushed image as <image>\n```\n\nNote that `100%` will always be displayed to indicate completion of each layer.\n\n### Alternative considerations\n\n- display byte-completion (eg. `10MB/100MB`)\n- display with time (eg. `25% (10s)`)\n- display with progress bar (eg. `[=====                   ] 10MB/100MB (15s)`)\n"
  },
  {
    "path": "proposals/archives/reproducible_base_image.md",
    "content": "# Proposal: Address Base Image Reproducibility\n\nImplemented in: **v0.9.0**\n\n## Motivation\n\nOne of the main goals of Jib is to be able to build images reproducibly, such that the same contents always creates the same images. It does this by wiping the timestamps and user information from the files in the Java application layers (dependencies, resources, classes). However, it does not do the same for the base image layers, which, by default, are from the latest [`gcr.io/distroless/java`](gcr.io/distroless/java) image. This may be unexpected behaviors since by default, reproducibility is on (the user may switch it off using the `enableReproducibleBuilds` parameter for Maven or the `reproducible` parameter for Gradle).\n\n### Terminology\n\nImage **reference** refers to the full reference for an image. This can be as short as `busybox` (which refers to the `library/busybox` **repository** on the Docker Hub **registry**), or as long as `gcr.io/distroless/java@sha256:0135c8b1adb3ed906f521973f825cea3fcdcb9b0db2f4012cc05480bf4d53fd6` (which refers to the image with **digest** `sha256:0135c8b1adb3...` in the `distroless/java` repository on the `gcr.io` registry). An image reference without a specific digest or tag, like `gcr.io/distroless/java`, defaults to the `latest` **tag**, which always refers to the newest digest in that repository.\n\n## Problem\n\nThe main problem is that the reproducibility feature of Jib does not actually guaranteed *for the image*, but rather only guarantees reproducibility *for the application layers*. This is a bug.\n\nThe problem arises in a common workflow where the developer expects reproducibility:\n\n1. The developer commits a change as version 123.\n1. The developer builds the image for that commit - results in image A.\n1. On another machine (possibly in prod), that developer checks out version 123 and builds the image - this should have resulted in image A again.\n\nHowever, since Jib uses the latest version of the [gcr.io/distroless/java](gcr.io/distroless/java) image (which is updated rather frequently - about every 2 weeks) as the base image to build the application layers on top of, if a newer [gcr.io/distroless/java](gcr.io/distroless/java) is latest, the rebuild would result in a different image than expected.\n\n## Goals\n\n- Maintain ease-of-use (no unnecessary extra configuration, at least for the default case)\n- Preferable: Keep reproducibility on by default\n\n## Solution\n\nJib will still use `gcr.io/distroless/java` by default, since in development, users may wish to keep at the latest base image. An alternative would be to use a specific digest of `gcr.io/distroless/java` but that would involve tying a version of Jib to a version of distroless.\n\nThe `reproducible`/`enableReproducibleBuilds` configuration will be removed. Application layers (dependencies, resources, classes) will always be reproducible.\n\nReproducibility will be guaranteed if the user specifies a specific digest to use for a base image. This can be specified as a fully-qualified custom base image, or as a `tag` configuration (Maven).\n\nThe user will be warned if the base image used is tagged with `latest` such that reproducibility is not guaranteed. Note that this warning is given by default.\n\nSo, the logic flow would be:\n\n1. Jib uses `gcr.io/distroless/java` as the base image.\n1. If the user specifies a different image to use as the base image, use that.\n1. The user can configure a specific digest to use - `tag` for Maven, and `from.image` for Gradle.\n1. If the final tag/digest is still `latest`, warn the user that reproducibility is not guaranteed due a changeable base image, and suggest the user to specify a specific digest.\n\n## Implementation\n\n- Remove the `reproducible`/`enableReproducibleBuilds` configuration and always build application layers reproducibly.\n- When validating the `jib-maven-plugin`/`jib-gradle-plugin` configuration, warn the user if the base image uses a `latest` tag.\n"
  },
  {
    "path": "proposals/archives/reproducible_jars.md",
    "content": "# Proposal: Make sub project/module jars included in container reproducible\n\nRelevant issue: [#1945](https://github.com/GoogleContainerTools/jib/issues/1945)  \nRelevent PR: [#2070](https://github.com/GoogleContainerTools/jib/pull/2070)\n\n| ❌ Aborted ❌ |\n| :----- |\n| This proposal is archived but unimplemented, in favor of documentation in the multimodule example: https://github.com/GoogleContainerTools/jib/tree/master/examples/multi-module |\n\n## Motivation\n\nCurrently in multimodule builds, jib takes the jars produced by submodule (and subproject) directly from\nthe build system. This means if the build system (gradle, maven) doesn't produce reproducible jars, and\njib includes them in the final container, the reproducibility guarantees of jib are not satisfied.\n\nWhat we want to do is to rewrite these jars to remove any information that could make builds not reproducible.\n\n## Requirements\n\nIn order to achieve reproducibility for jars, we need to:\n1. Remove timestamps and preserve file order. This can be achieved by setting the time to a fixed value\n   and sorting zip entries alphabetically\n2. Remove any changeable values from META-INF/MANIFEST.MF like build time, etc\n\n## Non-goals\n\nSigned archives will/should not be touched by this process. While we expect that submodule dependencies will\nnot be signed, there's no way for us to know what all users are doing eveywhere. A warning should be presented\nto the user that signed archives are processed by jib to be reproducible.\n\n## Current solution\n\nCurrently the user must configure their build to be reproducible. (we will borrow from these two solutions in our own solution with\nproper attribution)\n\n### Gradle\nIn gradle that means configuring the jar task explicitly: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html\n\n```\njar {\n  preserveFileTimestamps = false\n  reproducibleFileOrder = true\n}\n```\nand removing any changeable meta data from the MANIFEST.MF that they may have previously configured.\n\n### Maven\n\nIn maven, a user must configure an external plugin to do this, one could use: https://github.com/zlika/reproducible-build-maven-plugin\non all submodules and potentially achieve this on their own.\n\n## Proposed Solution\n\nJib should just handle this itself on the `PROJECT_DEPENDENCIES` layer. For all jars included in the layer, Jib needs to:\n\n1. Inspect the jar's `MANIFEST.MF` and remove values that could change.\n2. Force a single timestamp on all files\n3. Order the files in the jar(zip) by name (alphabetical)\n\nJib *will not* do this for any other layer\n\n### Configuration\n\nJib can make this configurable, but it should be `true` by default. A system property like\n\n```\n-Djib.projectDependencies.reproducible=true/false\n```\n\n### Implementation\n\n1. A utility in jib-core to convert jars to *reproducible* jars.\n1. This utility only impacts the `PROJECT_DEPENDENCIES` layer\n1. Triggering this utility at one of two places\n    1. During layer writing by introducing some new configuration into `LayerConfiguration` -- potentially more performant\n    1. At the plugin level to rewrite the jar before presenting it to the Jib Containerizer\n\n## Potential Issues\n\n1. Maybe we should just direct users to configure their builds to be reproducible using the gradle/maven mechanism desribed above.\n2. Can we *really, truly* know every value thrown into the manifest by the user??\n"
  },
  {
    "path": "proposals/archives/skaffold_config.md",
    "content": "# Proposal: Control over skaffold tasks/goals\n\nRelevant issue: https://github.com/GoogleContainerTools/skaffold/issues/3457\n\n## Motivation\n\nJib makes assumptions about what files to watch based on what it knows. Users\nmay have other ideas about intermediate build processes that occur before jib is\nready to process anything. Allowing users to configure the skaffold tasks to\ncorrectly reflect what their build is doing could help our users use skaffold\nmore effectively.\n\n## Current Configuration\n\nNone: Jib does not have any way to configure `_skaffold` tasks\n\n## Proposed Configuration\nThe proposal is to allow users to configure what jib shares with skaffold\n1. Allowing inclusion/exclusion on top of the jib defaults for files to watch\n2. Allow exclusion of files that will be sync'd\n\nThe final jib output will not deviate from what skaffold expects, but just\nallows for tighter control of what is sent to skaffold from jib.\n\n#### Gradle (`build.gradle`)\n```groovy\njib {\n  ...\n  skaffold {\n    watch {\n      buildIncludes = 'script.gradle'\n      includes = project.files('my/custom/inputs')\n      excludes = ['some/file/i/dont/want/watched']\n    }\n    sync {\n      exclude = 'a/file'\n    }\n  }\n}\n```\n\n#### Maven (`pom.xml`)\n```xml\n<configuration>\n  <skaffold>\n    <watch>\n      <buildIncludes>\n        <buildInclude>some/pomfile.xml</buildInclude>\n      </buildIncludes>\n      <includes>\n        <include>some/file</include>\n        <include>another/file</include>\n      </includes>\n      <excludes>\n        <exclude>not/me</exclude>\n        <exclude>/absolute/path/to/not/me</exclude>\n      <excludes>\n    </watch>\n    <sync>\n      <excludes>\n        <exclude>some/file</exclude>\n      <excludes>\n    </sync>\n  </skaffold>\n</configuration>\n```\n\n## Changes to Skaffold\n\nNone\n"
  },
  {
    "path": "proposals/buildfile.md",
    "content": "# Jib CLI Buildfile Specification\n\nSpecification for a YAML buildfile describing building a container image. This buildfile can be\nused by the jib-cli to generate a container using jib-core.\n\n```yaml\napiVersion: jib/v1alpha1\nkind: Buildfile\n\n# \"FROM\" with detail for manifest lists or multiple architectures\nfrom:\n  image: \"eclipse-temurin:11-jre\"\n  # optional: if missing, then defaults to `linux/amd64`\n  platforms:\n    - architecture: \"arm\"\n      os: \"linux\"\n    - architecture: amd64\n      os: darwin\n\n# potentially simple form of \"FROM\" (based on ability to define schema)\nfrom: \"eclipse-temurin:11-jre\"\n\ncreationTime: 0 # millis since epoch or iso8601 creation time\nformat: Docker # Docker or OCI\n\nenvironment:\n  \"MY_KEY1\": \"value\"\n  \"MY_KEY2\": \"value\"\nlabels:\n  \"com.example.owner\": \"person\"\n  \"com.example.mode\": \"dev\"\nvolumes:\n  - \"/myvolume\"\n  - \"/youvolume\"\n\nexposedPorts:\n  - \"123/udp\"\n  - \"456\"\n  - \"789/tcp\"\n\nuser: root\nworkingDirectory: \"/somewhere\"\nentrypoint:\n  - \"java\"\n  - \"-jar\"\ncmd:\n  - \"myjar.jar\"\n\nlayers:\n  properties:\n    # file properties applied to all layers\n    filePermissions: \"644\"\n    directoryPermissions: \"755\"\n    user: \"0\"\n    group: \"0\"\n    timestamp: \"0\"\n  entries:\n    - name: \"scripts and classes\"\n      # file properties only applied to this layer \"scripts and classes\"\n      properties:\n        filePermissions: \"333\"\n        directoryPermissions: \"777\"\n        user: \"goose\"\n        group: \"3\"\n        timestamp: \"2020-06-03T19:31:50+00:00\"\n      files:\n        - src: \"target/scripts\"\n          dest: \"/app/scripts\"\n          # file properties only applied to this copy directive\n          properties:\n            filePermissions: \"777\"\n          # another copy for the same layer, with includes and excludes\n        - src: \"target/classes\"\n          dest: \"/app/classes\"\n          excludes:\n            - \"**/goose.class\"\n            - \"**/moose.class\"\n          includes:\n            - \"**/*.class\"\n\n      # another layer, only globally defined file permissions are applied here\n    - name: \"other\"\n      files:\n        - src: \"build/other\"\n          dest: \"/app\"\n\n      # a archive layer using a tar, default mediaType\n    - name: \"some tar layer\"\n      archive: \"build/generated.tar\"\n\n      # a foreign layer using the optional mediatype for archive layers\n    - name: \"some foreign layer\"\n      mediaType: \"application/vnd.docker.image.rootfs.foreign.diff.tar.gzip\"\n      archive: \"https://somewhere.com/layer\"\n      # should we include size and digest here? I guess we can always ad tings\n```\n\n## Layers\n\n`layers.entries` are a list of `layer` directives\n\n`layer` directives can be `archive` or `file` layers\n\na `archive` layer consists of 3 parts\n* `name`: the name/description of the layer (metadata), it will also be used to populate the history entry for the layer\n* `archive`: a tar file to include as a layer\n* `mediatype`: the mediatype of archive\n\na `file` layer consists of 3 parts\n* `name`: the name of the layer (metadata)\n* `properties`: on disk metadata for all files in the layer\n* `files`: a list of *copy directies* (see below)\n\n`files` are a list of *copy directives* that consist of 2 required and 3 optional parts\n* `dest` *required*: an absolute path to copy the files to on the container\n* `src` *required*: a file, or directory on disk to copy from\n* `includes`: only includes the patterns matched in this parameter\n* `exludes`: excludes all files (higher prescendence than `includes`) matched in this parameter\n* `properties`: FilesProperties to represent on disk metadata for all files in this copy directive\n\n### File Copy Behavior\n\n`src`: filetype determined by type on local disk\n`dest`: \n - if `src` is directory, dest is always considered a directory\n - if `src` is file\n   - if `dest` ends with `/` then it is considered a target directory, file will be copied into directory\n   - if `dest` doesn't end with `/` then is is the target file location, `src` file will be copied and renamed to `dest`\n\n### FileProperties\n\nA list of properties that can be user specified for each file in a file layer\n* filePermissions: An octal representing permissions for files\n* directoryPermissions: An octal representing permissions for directories\n* user: The ownership user property\n* group: The ownership group property\n* timestamp: millis since epoch or iso8601 creation time\n\nFile properties are available at 3 levels\n* Global (`layers.properties`): applies to all layers\n* Layer (`layers.<entry>.properties`): applies to a single layer\n* Copy (`layers.<entry>.<copy>.properties`): applies to a single copy directive\n\nEach property (`filePermissions`, `directoryPermissions`, etc) can be defined at any level and are resolved in the follow order\n- All properties in `Copy` are applied first\n- Any properties not in `Copy` are applied from `Layer`\n- Any properties not in `Copy` or `Layer` are applied from `Global`\n- Any properties not defined anywhere use jib system defaults.\n\n### Base image value inheritance\nThe value(s) defined in the base image are preserved and propagated into the\nconfig of the new container.\n\nThe behavior of the buildfile values post-inheritance must be considered\n\nThese parameters will allow appending to the base image value:\n- `volumes`\n- `exposedPorts`\n\nThese parameters will allow appending for new keys, and overwriting for existing keys:\n- `labels`\n- `environment`\n\nThese parameters will be overwritten:\n- `user`\n- `workingDirectory`\n- `entrypoint`\n- `cmd`\n\nIf we start getting specific user requests to control this, we can explore\ninheritance control in the future.\n\n## Extended features (not included in first pass)\n\n### Other time options\n* `actual`: use timestamp from file on disk\n* `current`: use time of build\n\n### Platform\n\n#### Handle sub categories for platforms\nUsers should be able to further specify details for selecting platforms such as `os.version`, `os.features`, `variant` and `features`\n```\nplatforms:\n  - architecture: \"arm\"\n    os: \"linux\"\n    os.version: \"a\"\n    os.features:\n      - \"b1\"\n      - \"b2\"\n    variant: \"c\"\n    features:\n      - \"d1\"\n      - \"d2\"\n```\n\n#### Special platform specific layers\nLayer entries can contain platform specific filters that are only applied for builds matching that platform\n\n```yaml\nentries:\n  # arm things will only apply to builds for arm achitectures\n  - name: \"arm things\"\n    platform:\n      architecture: arm\n    ...\n```\n\n#### Mark values for erasure\nIf a user does not want to inherit specific values from the base image a mechanism for *erasing* them could be useful.\n"
  },
  {
    "path": "proposals/cli-jar-processing.md",
    "content": "# Proposal : Jib CLI Jar Processing \n\nRelevant Issue: [#2796](https://github.com/GoogleContainerTools/jib/issues/2796)\n\n# Motivation \nAllow users to containerize arbitrary jar files without having to integrate Jib into java build tools (maven and gradle) or use a `.yaml` jib cli buildfile.\n\n# Proposal Changes\n\n## Standard Jar \nA standard jar can be containerized in two modes, exploded or packaged. \n\n### Exploded Mode\nAchieved by calling `jib jar ${JAR_NAME}.jar --target ${TARGET_REGISTRY}`\nThe default mode for containerizing a jar. It will explode a jar into the following layers:  \n- Dependencies Layer: Contains dependencies whose versions do not contain `SNAPSHOT`. Note that this layer will not be created if `Class-Path` is not present in the manifest.\n- Snapshot-Dependencies Layer: Contains dependencies whose versions contain `SNAPSHOT`. Note that this layer will not be created if `Class-Path` is not present in the manifest.\n- Resources Layer: Contains resources parsed from jar file. Note that it will also include `MANIFEST.MF`.\n- Classes Layer: Contains classes parsed from jar file. \n\n**Entrypoint** : `java -cp /app/dependencies/:/app/explodedJar/ ${MAIN_CLASS}`\n\n### Packaged Mode\nAchieved by calling `jib jar ${JAR_NAME}.jar --target ${TARGET_REGISTRY} --mode packaged`.\nIt will result in the following layers on the container:\n- Dependencies Layer: Contains the dependencies derived from `Class-Path` in jar manifest. Note that this layer will not be created if `Class-Path` is not present in the manifest.\n- Jar Layer: Contains the original jar.\n\n**Entrypoint** : `java -jar ${JAR_NAME}.jar`\n\n## Spring-Boot Fat Jar\nA Spring-Boot Fat Jar can be containerized in two modes, exploded or packaged. \n\n### Exploded Mode\nAchieved by calling `jib jar ${JAR_NAME}.jar --target ${TARGET_REGISTRY}`\nThe default mode for containerizing a jar. It will explode a jar according to what is specified in the `layers.idx` file of the jar, if present, or according to following format:\n- Dependencies Layer: For a dependency whose version does not contain `SNAPSHOT`.\n- Spring-Boot-Loader Layer: Contains jar loader classes.\n- Snapshot-Dependencies Layer: For a dependency whose version contains `SNAPSHOT`.\n- Resources Layer: Contains resources parsed from `BOOT-INF/classes/` in the jar and `META-INF/`.\n- Classes Layer: Contains classes parsed from `BOOT-INF/classes/` in the jar.\n\n**Entrypoint** : `java -cp /app org.springframework.boot.loader.JarLauncher`\n\n### Packaged Mode\nAchieved by calling `jib jar ${JAR_NAME}.jar --target ${TARGET_REGISTRY} --mode packaged`\nIt will containerize the jar as is. However, **note** that we highly recommend against using packaged mode for containerizing spring-boot fat jars. \n\n**Entrypoint**: `java -jar ${JAR_NAME}.jar`\n\n### Optional Parameters\nThe `jar` command also provides the option to configure the parameters listed below.  \n\n```\n    --from                    The base image to use\n    --jvm-flags               JVM arguments, example: --jvm-flags=-Dmy.property=value,-Xshare:off.\n    --expose                  Ports to expose on container, example: --expose=5000,7/udp.\n    --volumes                 Directories on container to hold extra volumes,  example: --volumes=/var/log,/var/log2.\n    --environment-variables   Environment variables to write into container, example: --environment-variables env1=env_value1,env2=env_value2.\n    --labels                  Labels to write into container metadata, example: --labels=label1=value1,label2=value2.\n-u, --user                    The user to run the container as, example: --user=myuser:mygroup.\n    --image-format            Format of container, example --image-format=OCI. Overrides the default (Docker).\n    --program-args            Program arguments for container entrypoint.\n    --entrypoint              Entrypoint for container. Overrides the default entrypoint, example: --entrypoint='custom entrypoint'.\n    --creation-time           The creation time of the container in milliseconds since epoch or iso8601 format. Overrides the default (1970-01-01T00:00:00Z).\n```\n"
  },
  {
    "path": "proposals/container-build-plan-spec.md",
    "content": "# Container Build Plan Specification\n\nSpecification for building a container image.\n\nAlthough looking similar, the structure and semantics of similarly named properties are different from the Docker/[OCI Image Configuration](https://github.com/opencontainers/image-spec/blob/master/config.md).\n\n## Example\n\n```\n{\n  \"baseImage\": \"eclipse-temurin:11-jre@sha256:a3036d6a01859e3fe8cbad4887ccb2e4afd5f5ce8f085ca6bc0302fcbb8601f7\",\n  \"architectureHint\": \"amd64\",\n  \"osHint\": \"linux\",\n  \"format\": \"Docker\",\n  \"created\": \"2011-12-03T22:42:05Z\",\n\n  \"config\": {\n    \"env\": {\n      \"KEY\": \"value\",\n      \"PATH\": \"/usr/sbin:/usr/bin:/sbin:/bin\",\n      \"HOME\": \"/home/guest\"\n    },\n    \"labels\": {\n      \"com.example.department.some-label-key\": \"avocado explosion\",\n      \"com.example.system.xml\": \"<message>delivered</message>\"\n    },\n    \"volumes\": [\"/mnt/shared\", \"/tmp\"]\n    \"exposedPorts\": [\"8080\", \"53/udp\", \"80/tcp\"]\n    \"user\": \":12345\",\n    \"workingDir\": \"/\n    \"entrypoint\": [\"/bin/bash\", \"-c\"],\n    \"cmd\": [\n      \"-x\",\n      \"set -o errexit ; echo \\\"\\$0=$0 \\$1=$1\\\" ; echo \\\"\\$PATH=${PATH}\\\" ; exit 0\",\n      \"my-shell-name\",\n      \"first shell arg\"\n    ],\n  },\n\n  \"layers\": [\n    {\n      \"type\": \"fileEntries\"\n      \"entries\": [\n        {\n          \"src\": \"/home/jane/workspace/bin/Main.class\",\n          \"dest\": \"/app/classes/Main.class\",\n          \"modificationTime\": \"2019-07-15T10:15:30+09:00\",\n          \"permissions\": \"600\",\n        },\n        {\n          \"src\": \"/home/jane/libs/util-1.0.jar\",\n          \"dest\": \"/app/jars/util.jar\",\n          \"modificationTime\": \"2011-12-03T22:42:05Z\",\n          \"permissions\": \"644\",\n        }\n      ]\n    },\n    {\n      \"type\": \"layerArchive\"\n      \"mediaType\": \"...\",\n      \"path\": \"/home/jane/misc/cacerts.tar\",\n    },\n    {\n      \"type\": \"fileEntries\"\n      \"entries\": [\n        {\n          \"src\": \"/home/workspace/scripts/run.sh\",\n          \"dest\": \"/app/run.sh\",\n          \"modificationTime\": \"2011-12-03T22:42:05Z\",\n          \"permissions\": \"777\",\n          \"ownership\": \"\"\n        },\n      ]\n    },\n    ...\n  ]\n}\n```\n\n## Build Plan Object\n\n* `baseImage`: string\n\n   - `null` or omitted: no base image (from \"scratch\")\n\n* `architectureHint`: string\n\n   - If the base image reference is an \"image list\" (Docker manifest list or an OCI image index), must be set so that an image builder can select the image matching the given architecture.\n   - If the base image reference is not an \"image list\", this value is ignored and the architecture of the built image follows that of the base image.\n\n   The default is \"amd64\" when omitted.\n\n* `osHint`: string\n\n   - If the base image reference is an \"image list\" (Docker manifest list or an OCI image index), must be set so that an image builder can select the image matching the given OS.\n   - If the base image reference is not an \"image list\", this value is ignored and the OS of the built image follows that of the base image.\n\n   The default is \"linux\" when omitted.\n\n* `format`: string\n\n   Image output format. Either `\"Docker\"` or `\"OCI\"`.\n\n   - `null` or omitted: `\"Docker\"` by default\n\n* `created`: string\n\n   ISO 8601-like date-time format.\n\n   - `null` or omitted: the epoch (`\"1970-01-01T00:00:00Z\"`) by default\n\n* `config`: [Execution Parameters object](#execution-parameters-object)\n\n   Can be `null` or omitted.\n\n* `layers`: array of [Layer Configuration objects](#layer-configuration-object)\n\n   Adds layers on top of those from the base image; it is not possible to remove layers from the base image.\n\nA builder implementation must inherit the [`history` entries](https://github.com/opencontainers/image-spec/blob/master/config.md) of base image layers.\n\n### Execution Parameters Object\n\n* `env`: map of (string, string)\n\n   Adds environment variables on top of those from the base image (hence overridable for same variable names); it is not possible to unset variables from the base image.\n\n* `labels`: map of (string, string)\n\n   Adds labels on top of those from the base image (hence overridable for same label keys); it is not possible to unset labels from the base image.\n\n* `volumes`: array of strings\n\n   Adds volumes on top of those from the base image (no duplicate entries possible); it is not possible to unset volumes from the base image.\n\n* `exposedPorts`: strings\n\n   Adds ports on top of those from the base image; it is not possible to unset ports from the base image.\n\n* `user`: string\n\n   - `null` or omitted: inherits from the base image.\n   - Otherwise (including an empty string `\"\"`, `\":\"`, `\"<user>:\"`, and `\":<group>\"`), sets the given user and group; it is not possible to only inherit either the user or the group.\n\n* `workingDir`: string\n\n   - `null` or omitted: inherits from the base image.\n   - Otherwise (including an empty string `\"\"`), sets the given directory.\n\n* `entrypoint`: array of strings\n\n   - `null` or omitted: inherits from the base image.\n   - Otherwise (including an empty list `[]` and `[\"\"]`), sets the given entrypoint. (Note, if `cmd` is not given, also sets `cmd` to `null`.)\n \n   Note `[]` is different from the `Dockerfile` build behavior. `Dockerfile` build sets the entrypoint to `null` if given `ENTRYPOINT []`.\n\n* `cmd`: array of strings\n\n   | `entrypoint`      | `cmd`             | `cmd` set in container |\n   |-------------------|-------------------|------------------------|\n   |                   | defined           | given value            |\n   | defined           | `null` or omitted | `null`                 |\n   | `null` or omitted | `null` or omitted | inherited              |\n\n   Note `[]` for `entrypoint` and `cmd` is considered \"defined\".\n\n### Layer Configuration Object\n\n* `type`: string\n\n   - `fileEntries` (collection of files as a layer), `layerArchive` (an archive file as a laye, to be implemented), etc.\n\n#### `fileEntries` Sub-Type of Layer Configuration Object\n\n* `entries`: array of [Layer Entry objects](#layer-entry-object)\n\n### Layer Entry Object\n\n* `src`: single local file, required\n* `dest`: path in the container, required\n* `permissions`: POSIX permissions, required\n* `modificationTime`: if `null` or omitted, the epoch + 1 second by default\n* `ownership`:\n   - If `null`, omitted, or an empty string `\"\"`, then effectively equivalent to `\"0:0\"` (`\"root:root\"`).\n   - Otherwise, in the form of `\"<user>:<group>\"` where `<user>` and `<group>` are optional. When `<user>` or `<group>` is omitted, it is equivalent to `0` (`root`).\n"
  },
  {
    "path": "proposals/jib-cli-surface.md",
    "content": "# Jib CLI Surface Specification\n\nSpecification for a command structures exposed to users of the jib-cli\n\n## Usage\n`jib COMMAND [OPTIONS]`\n\n## Commands\n\nThe jib cli exposes the following top level commands\n```\nbuild        build a container\n```\n\n## Options\n\n### Required\n```\n-t  --target <image-ref>  a target image reference, using the url schema defined in jib\n                          gcr.io/project/image (default is build to registry)\n                          registry://gcr.io/project/image\n                          docker://some-image-ref\n                          tar://relative/path.tar\n                          tar:///absolute/path.tar\n```\n\n### Optional\n\n\n#### Build Config\n```\n    --additional-tags <tag1>[,<tag2>,...]  additional tags for target\n    --application-cache <directory>        location of the application cache (jib default is temp directory)\n    --base-image-cache <directory>         location of the base image cache (jib default is user cache)\n-b, --build-file <file>                    location of the build file (default <context>/jib.yaml)\n-c, --context <dir>                        location of the build context (default .)\n    --docker-config <directory>            location of docker configuration\n    --name <image-ref>                     image name to bake into tar file, required when using \"-t tar://...\" \n-p, --parameter <name>=<value>             templating parameters replace `${name}` with `value` in the build file (repeatable)\n```\n\n#### Auth/Security\n```\n    --allow-insecure-registries            allow jib to communicate with registries over https\n    --send-credentials-over-http           allow jib to send credentials over http (used in conjunction with --allow-insecure-registries)\n```\n##### Credentials\n\nCredentials can be specified using credential helpers or username + password. The following options are available\n```\n    --credential-helper <credHelper>       credential helper to use for registries, a path or name suffix (docker-credential-<suffix>)\n    --to-credential-helper <credHelper>    credential helper to use only for the target registry\n    --from-credential-helper <credHelper>  credential helper to use only for the base image registry\n\n    --username <username>                  configure a username for authenticating against registries\n    --password <password>                  configure a password for authenticating against registries (interactive if <password> is omitted)\n    --to-username <username>               configure a username for authenticating on the registry that an image is being built to\n    --to-password <password>               configure a password for authenticating on the registry and image is being built to (interactive if <password> is omitted)\n    --from-username <username>             configure a username for authentication on the registry that a base image is being sourced from\n    --from-password <password>             configure a password for authentication on the registry that a base image is being sourced from (interactive if <password> is omitted)\n```\ncombinations of `credential-helper`, `username` and `password` flags come with restrictions and can be use only in the following ways:\n\nOnly Credential Helper\n1. `--credential-helper`\n1. `--to-credential-helper`\n1. `--from-credential-helper`\n1. `--to-credential-helper`, `--from-credential-helper`\n\nOnly Username and Password\n1. `--username`, `--password`\n1. `--to-username`, `--to-password`\n1. `--from-username`, `--from-password`\n1. `--to-username`, `--to-password`, `--from-username`, `--from-password`\n\nMixed Mode\n1. `--to-credential-helper`, `--from-username`, `--from-password`\n1. `--from-credential-helper`, `--to-username`, `--to-password`\n\n\n#### Info Params\n```\n    --help                  print usage and exit\n    --console <type>        set the console type (auto (default), plain, rich)\n    --verbosity <level>     set logging verbosity (quiet, error, warn, lifecycle (default), info, debug)\n-v, --version               print version information and exit\n```\n\n#### Debugging Params (hidden in help)\n```\n    --stacktrace            print stacktrace on error (for debugging issues in the jib-cli)\n    --http-trace            enable http tracing at level=config, output=console\n    --serialize             run jib in serialized mode\n```\n\n### Flag Files\n\nPico cli allows the use of flag files (https://picocli.info/#AtFiles). The jib cli will enable this feature. This will allow allow build environment configuration to be defined in a configuration file.\n\n## Environment Variables\n\n### Docker daemon specification\n\nInformation on communicating with the docker daemon is determined using the `DOCKER_*` environment variables.\n\n## Other considerations\n\n- Users have asked to warm up the cache, perhaps we can have a `cache` command option\n- In the future we should be able to build `jar`s from the command line\n- Default application cache directory is a temporary directory (will lead to slow rebuilds), we should do a better job of defining a default here.\n"
  },
  {
    "path": "proposals/tags-on-existing-images.md",
    "content": "# Proposal: Control the pushing of tags on existing images in target registry\n\nRelevant issues: [#2360](https://github.com/GoogleContainerTools/jib/issues/2360)\n\n## Motivation\nCurrently Jib pushes tags for all built images, irrelevant of whether the image is already \npresent on the target registry. \n\nAs highlighted by @loosebazooka [#2360](https://github.com/GoogleContainerTools/jib/issues/2360) \nthis can be particularly annoying if some automated orchestrator is monitoring the registry\nand releases any new tags found. This issue is especially prevalent in cases where the tags are\nauto-generated by CI systems and added with every triggered build.\n\n## Proposal\nThe idea is to give the user the ability to control this functionality by means of a switch/option. \nThere might be use-cases where the current behaviour is preferable, eg, where the tags follow semantic \nversioning, or where the tags are not automatically generated. Hence, adding a switch seems to be \na better solution rather than changing the current behaviour.\n\n## Proposed changes\nA new system property is to be added to control this new behaviour:\n`-Djib.skipExistingImages`\n\nA new step is created with the below signature:\n`class CheckImageStep implements Callable<BuildResult>`, where its `call()` function performs a \ncall to the `RegistryClient`. This step is executed in the `StepsRunner.pushImages()` method, \nbefore the `PushImageStep.makeList()` part, only if the `skipExistingImages` flag is set to true. \nDepending on the `BuildResult` of this new step, the `PushImageStep` futures are then created (or not).\n\nThe registry call used in `CheckImageStep` can be implemented in various ways. It is being \nproposed that a new `checkManifest` function is added in `RegistryClient`. This should execute \na `ManifestChecker`, also a new class. The `ManifestChecker`'s `handleHttpResponseException` class\nhandles the `HTTP/404` scenario, in cases where the image being checked is not found. "
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        mavenCentral()\n        // Workaround from: https://github.com/gradle/gradle/issues/15406#issuecomment-1020352934\n        gradlePluginPortal {\n            this as MavenArtifactRepository\n            metadataSources {\n                mavenPom()\n                artifact()\n                ignoreGradleMetadataRedirection()\n            }\n        }\n    }\n}\n\ninclude \":jib-build-plan\"\ninclude \":jib-plugins-extension-common\"\ninclude \":jib-gradle-plugin-extension-api\"\ninclude \":jib-maven-plugin-extension-api\"\ninclude \":jib-core\"\ninclude \":jib-plugins-common\"\ninclude \":jib-gradle-plugin\"\ninclude \":jib-maven-plugin\"\ninclude \":jib-cli\"\n"
  }
]