diff --git a/.allstar/binary_artifacts.yaml b/.allstar/binary_artifacts.yaml new file mode 100644 index 0000000000..0b70480559 --- /dev/null +++ b/.allstar/binary_artifacts.yaml @@ -0,0 +1,4 @@ +# Ignore reason: jars are used for testing purposes only +ignorePaths: + - jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/libs/dependency-1.0.0.jar + - jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/libs/dependency-1.0.0.jar \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue_report.md b/.github/ISSUE_TEMPLATE/issue_report.md index 73c5bbeffe..804b3a3f1e 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.md +++ b/.github/ISSUE_TEMPLATE/issue_report.md @@ -8,7 +8,13 @@ assignees: '' --- +Fixes # 🛠️ diff --git a/.github/RELEASE_TEMPLATES/cli_release_checklist.md b/.github/RELEASE_TEMPLATES/cli_release_checklist.md index 2b9b33d564..1252a51182 100644 --- a/.github/RELEASE_TEMPLATES/cli_release_checklist.md +++ b/.github/RELEASE_TEMPLATES/cli_release_checklist.md @@ -4,6 +4,7 @@ labels: release --- ## Requirements - [ ] ⚠️ Ensure the release process has succeeded before proceeding +- [ ] ⚠️ Publish [Release]({{ env.RELEASE_DRAFT }}) after adding CHANGELOG entries ([example](https://github.com/GoogleContainerTools/jib/releases/tag/v0.8.0-cli)) ## GCS - [ ] Run {{ env.GCS_UPDATE_SCRIPT }} script to update GCS with the latest version number @@ -11,11 +12,4 @@ labels: release ## Github - [ ] Update [CHANGELOG.md]({{ env.CHANGELOG_URL }}) - [ ] Update [README.md]({{ env.README_URL }}) -- [ ] Publish [Release]({{ env.RELEASE_DRAFT }}) - [ ] Merge PR ({{ env.RELEASE_PR }}) -- [ ] Update the current [milestone](https://github.com/GoogleContainerTools/jib/milestones), roll over any incomplete issues to next milestone. - -## Announce -- [ ] Email jib users -- [ ] Post to [gitter](https://gitter.im/google/jib) -- [ ] Mention on all fixed issues that latest release has addressed the issue (usually any closed issues in a [milestone](https://github.com/GoogleContainerTools/jib/milestones)) diff --git a/.github/RELEASE_TEMPLATES/core_release_checklist.md b/.github/RELEASE_TEMPLATES/core_release_checklist.md index f2da093216..bf696ace77 100644 --- a/.github/RELEASE_TEMPLATES/core_release_checklist.md +++ b/.github/RELEASE_TEMPLATES/core_release_checklist.md @@ -10,9 +10,3 @@ labels: release - [ ] Update [README.md]({{ env.README_URL }}) - [ ] Publish [Release]({{ env.RELEASE_DRAFT }}) - [ ] Merge PR ({{ env.RELEASE_PR }}) -- [ ] Update the current [milestone](https://github.com/GoogleContainerTools/jib/milestones), roll over any incomplete issues to next milestone. - -## Announce -- [ ] Email jib users -- [ ] Post to [gitter](https://gitter.im/google/jib) -- [ ] Mention on all fixed issues that latest release has addressed the issue (usually any closed issues in a [milestone](https://github.com/GoogleContainerTools/jib/milestones)) diff --git a/.github/RELEASE_TEMPLATES/plugin_release_checklist.md b/.github/RELEASE_TEMPLATES/plugin_release_checklist.md index 2909f7b114..c5e26e157c 100644 --- a/.github/RELEASE_TEMPLATES/plugin_release_checklist.md +++ b/.github/RELEASE_TEMPLATES/plugin_release_checklist.md @@ -11,20 +11,10 @@ labels: release ## Github - [ ] Update [CHANGELOG.md]({{ env.CHANGELOG_URL }}) - [ ] Update [README.md]({{ env.README_URL }}) -- [ ] Update [CONTRIBUTING.md](https://github.com/GoogleContainerTools/jib/blob/master/CONTRIBUTING.md) -- [ ] Update [examples](https://github.com/GoogleContainerTools/jib/tree/master/examples) +- [ ] Search/replace the old published version with the new published version in [examples](https://github.com/GoogleContainerTools/jib/tree/master/examples) - [ ] Publish [Release]({{ env.RELEASE_DRAFT }}) - [ ] Merge PR ({{ env.RELEASE_PR }}) -- [ ] Update the current [milestone](https://github.com/GoogleContainerTools/jib/milestones), roll over any incomplete issues to next milestone. - -#### Skaffold -- [ ] Update versions in Skaffold ([example PR](https://github.com/GoogleContainerTools/skaffold/pull/4639)) #### Jib-Extensions - [ ] Update versions in [Jib Extensions](https://github.com/GoogleContainerTools/jib-extensions) - [ ] 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) - -## Announce -- [ ] Email jib users -- [ ] Post to [gitter](https://gitter.im/google/jib) -- [ ] Mention on all fixed issues that latest release has addressed the issue (usually any closed issues in a [milestone](https://github.com/GoogleContainerTools/jib/milestones)) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 21e6eecbc6..1d76e03ecd 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,5 +6,5 @@ jobs: name: "Gradle wrapper validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.4 - - uses: gradle/wrapper-validation-action@v1.0.4 + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v3.5.0 diff --git a/.github/workflows/jib-cli-release.yml b/.github/workflows/jib-cli-release.yml index 6df79aa9a3..2284e1f78e 100644 --- a/.github/workflows/jib-cli-release.yml +++ b/.github/workflows/jib-cli-release.yml @@ -11,9 +11,12 @@ jobs: release: name: Release Jib CLI runs-on: ubuntu-latest + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - name: Check out code - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v4 - name: Build project run: | @@ -41,15 +44,19 @@ jobs: ./gradlew jib-cli:instDist --stacktrace cd jib-cli/build/distributions - sha256sum jib-${{ github.event.inputs.release_version }}.zip > zip.sha256 + mv jib-${{ github.event.inputs.release_version }}.zip jib-jre-${{ github.event.inputs.release_version }}.zip + sha256sum jib-jre-${{ github.event.inputs.release_version }}.zip > zip.sha256 + + - name: Generate SLSA subject for Jib CLI release binaries + id: hash + working-directory: jib-cli/build/distributions + run: echo "hashes=$(cat zip.sha256 | base64 -w0)" >> $GITHUB_OUTPUT - name: Create pull request - uses: repo-sync/pull-request@v2.6 + uses: repo-sync/pull-request@v2.12.1 id: create-pr with: - # Use a personal token to file a PR as a non-bot author to trigger other workflows (e.g., unit tests): - # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token; - github_token: ${{ secrets.GA_RELEASE_PR_PERSONAL_TOKEN }} + github_token: ${{ secrets.CLOUD_JAVA_BOT_GITHUB_TOKEN }} source_branch: cli-release-v${{ github.event.inputs.release_version }} pr_title: "CLI release v${{ github.event.inputs.release_version }}" pr_body: "To be merged after the release is complete." @@ -75,7 +82,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create-release.outputs.upload_url }} - asset_path: ./jib-cli/build/distributions/jib-${{ github.event.inputs.release_version }}.zip + asset_path: ./jib-cli/build/distributions/jib-jre-${{ github.event.inputs.release_version }}.zip asset_name: jib-jre-${{ github.event.inputs.release_version }}.zip asset_content_type: application/zip @@ -90,7 +97,7 @@ jobs: asset_content_type: text/plain - name: Create Jib CLI release checklist issue - uses: JasonEtco/create-an-issue@v2.6 + uses: JasonEtco/create-an-issue@v2.9.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_NAME: v${{ github.event.inputs.release_version }}-cli @@ -102,3 +109,32 @@ jobs: with: filename: .github/RELEASE_TEMPLATES/cli_release_checklist.md + provenance: + needs: [release] + permissions: + actions: read # To read the workflow path. + id-token: write # To sign the provenance. + contents: write # To add assets to a release. + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0 + with: + base64-subjects: "${{ needs.release.outputs.hashes }}" + + upload: + needs: [release, provenance] + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Download attestation + uses: actions/download-artifact@v3 + with: + name: "${{ needs.provenance.outputs.attestation-name }}" + + - uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.release.outputs.upload_url }} + asset_path: "${{ needs.provenance.outputs.attestation-name }}" + asset_name: "${{ needs.provenance.outputs.attestation-name }}" + asset_content_type: application/json diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 627b81e687..0248584a91 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v4 - name: Check input run: | @@ -59,14 +59,10 @@ jobs: -Prelease.releaseVersion=${{ github.event.inputs.release_version }} - name: Create pull request - uses: repo-sync/pull-request@v2.6 + uses: repo-sync/pull-request@v2.12.1 id: create-pr with: - # Use a personal token to file a PR to trigger other workflows (e.g., unit tests). - # Save your access token as GA_RELEASE_PR_PERSONAL_TOKEN. - # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token - # https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets - github_token: ${{ secrets.GA_RELEASE_PR_PERSONAL_TOKEN }} + github_token: ${{ secrets.CLOUD_JAVA_BOT_GITHUB_TOKEN }} source_branch: ${{ github.event.inputs.project }}-release-v${{ github.event.inputs.release_version }} pr_title: "${{ github.event.inputs.project }} release v${{ github.event.inputs.release_version }}" pr_body: "To be merged after the release is complete." @@ -92,7 +88,7 @@ jobs: See [CHANGELOG.md](https://github.com/GoogleContainerTools/jib/blob/master/jib-${{ github.event.inputs.project }}-plugin/CHANGELOG.md) for more details. - name: Create Maven/Gradle release checklist issue - uses: JasonEtco/create-an-issue@v2.6 + uses: JasonEtco/create-an-issue@v2.9.2 if: ${{ github.event.inputs.project == 'maven' || github.event.inputs.project == 'gradle' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -121,7 +117,7 @@ jobs: See [CHANGELOG.md](https://github.com/GoogleContainerTools/jib/blob/master/jib-core/CHANGELOG.md) for more details. - name: Create Core release checklist issue - uses: JasonEtco/create-an-issue@v2.6 + uses: JasonEtco/create-an-issue@v2.9.2 if: ${{ github.event.inputs.project == 'core' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b290de4945..0cb03a0d4e 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -17,21 +17,21 @@ jobs: steps: - name: Get current date id: date - run: echo "::set-output name=date::$(date +'%Y-%m-%d' --utc)" - - uses: actions/checkout@v2 + run: echo "date=$(date +'%Y-%m-%d' --utc)" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 11 - uses: actions/setup-java@v2.3.1 + uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: 11 - name: Cache SonarCloud packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar-${{ steps.date.outputs.date }} - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.m2/repository diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 8f6b27bc08..9d3d9eccf4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -17,14 +17,14 @@ jobs: # for gradle TERM: dumb steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v4 with: fetch-depth: 2 - - uses: actions/setup-java@v2.3.1 + - uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: ${{ matrix.java }} - - uses: actions/cache@v2.1.6 + - uses: actions/cache@v4 with: path: | ~/.m2/repository diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc7be5c206..aad54536bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,13 @@ + +This 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. + # Contributing to Jib -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. +Please follow the guidelines below before opening an issue or a PR: +1. Ensure the issue was not already reported. +2. 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. +3. 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. +4. 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. ## Contributor License Agreement @@ -15,25 +21,20 @@ You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. -## Building Jib +## Code Reviews -Jib comes as 3 public components: +All submissions, including submissions by project members, require review. We +use Github pull requests for this purpose. - - `jib-core`: a library for building containers - - `jib-maven-plugin`: a Maven plugin that uses `jib-core` and `jib-plugins-common` - - `jib-gradle-plugin`: a Gradle plugin that uses `jib-core` and `jib-plugins-common` +Before submitting a pull request, please make sure to: -And 1 internal component: +- Identify an existing [issue](https://github.com/GoogleContainerTools/jib/issues) to associate + with your proposed change, or [file a new issue](https://github.com/GoogleContainerTools/jib/issues/new). +- Describe any implementation plans in the issue and wait for a review from the repository maintainers. - - `jib-plugins-common`: a library with helpers for maven/gradle plugins +### Typical Contribution Cycle -The project is configured as a single gradle build. Run `./gradlew build` to build the -whole project. Run `./gradlew install` to install all public components into the -local maven repository. - -## Code Reviews - -1. Set your git user.email property to the address used for step 1. E.g. +1. Set your git user.email property to the address used for signing the CLA. E.g. ``` git config --global user.email "janedoe@google.com" ``` @@ -47,6 +48,22 @@ local maven repository. 5. Associate the change with an existing issue or file a [new issue](../../issues). 6. Create a pull request! +## Building Jib + +Jib comes as 3 public components: + +- `jib-core`: a library for building containers +- `jib-maven-plugin`: a Maven plugin that uses `jib-core` and `jib-plugins-common` +- `jib-gradle-plugin`: a Gradle plugin that uses `jib-core` and `jib-plugins-common` + +And 1 internal component: + +- `jib-plugins-common`: a library with helpers for maven/gradle plugins + +The project is configured as a single gradle build. Run `./gradlew build` to build the +whole project. Run `./gradlew install` to install all public components into the +local maven repository. + ### Integration Tests **Note** that in order to run integration tests, you will need to set one of the following environment variables: @@ -64,10 +81,14 @@ To run select integration tests, use `--tests=`, see [gradle docs]( # Development Tips +## Java version + +Use Java 8 or 11 for development. https://sdkman.io/ is a helpful tool to switch between Java versions. + ## Configuring Eclipse Although jib is a mix of Gradle and Maven projects, we build everything using one -unifed gradle build. There is special code to include some projects directly as +unified gradle build. There is special code to include some projects directly as source, but importing your project should be pretty straight forward. 1. Ensure you have installed the Gradle tooling for Eclipse, called @@ -122,11 +143,11 @@ To use a local build of the `jib-gradle-plugin`: } } ``` - 1. Modify your test project's `build.gradle` to use the snapshot version + 1. Modify your test project's `build.gradle` to use the [latest snapshot version](jib-gradle-plugin/gradle.properties) ```groovy plugins { - // id 'com.google.cloud.tools.jib' version '3.1.4' - id 'com.google.cloud.tools.jib' version '3.1.5-SNAPSHOT' + // id 'com.google.cloud.tools.jib' version 'major.minor.patch' + id 'com.google.cloud.tools.jib' version 'major.minor.patch-SNAPSHOT' } ``` diff --git a/README.md b/README.md index 4cd888d39b..e6633f53e4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ![Build Status](https://storage.googleapis.com/cloud-tools-for-java-kokoro-build-badges/jib-ubuntu-master-orb.svg) ![Build Status](https://storage.googleapis.com/cloud-tools-for-java-kokoro-build-badges/jib-windows-master-orb.svg) ![Build Status](https://storage.googleapis.com/cloud-tools-for-java-kokoro-build-badges/jib-macos-master-orb.svg) +[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev) [![Gitter version](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/google/jib) # Jib @@ -18,10 +19,12 @@ Jib 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. -[Maven](https://maven.apache.org/): See documentation for [jib-maven-plugin](jib-maven-plugin).\ -[Gradle](https://gradle.org/): See documentation for [jib-gradle-plugin](jib-gradle-plugin).\ -[Jib Core](jib-core): A general-purpose container-building library for Java.\ -[Jib CLI](jib-cli): A command-line interface for building images that uses Jib Core. +- [Maven](https://maven.apache.org/): See documentation for [jib-maven-plugin](jib-maven-plugin). +- [Gradle](https://gradle.org/): See documentation for [jib-gradle-plugin](jib-gradle-plugin). +- [Jib Core](jib-core): A general-purpose container-building library for Java. +- [Jib CLI](jib-cli): A command-line interface for building images that uses Jib Core. + +Jib works well with Google Cloud Build. For details, see [how to use Jib on Google Cloud Build](docs/google-cloud-build.md). 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)). @@ -54,7 +57,7 @@ The [examples](examples) directory includes the following examples (and more). ## How Jib Works -Whereas 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 the [AdoptOpenJDK](https://hub.docker.com/_/adoptopenjdk) base image, 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)). +Whereas 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)). See 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). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..09a1522637 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| jib-maven-plugin v3.x | :heavy_check_mark: | +| jib-gradle-plugin v3.x | :heavy_check_mark: | +| jib-core v0.x | :heavy_check_mark: | +| jib-cli v0.x | :heavy_check_mark: | + + +## Reporting a Vulnerability + +To report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz). +We use g.co/vulnz for our intake, and do coordination and disclosure here on +GitHub (including using GitHub Security Advisory). The Google Security Team will +respond within 5 working days of your report on g.co/vulnz. diff --git a/build.gradle b/build.gradle index f2e7c0c388..bfa4d3807e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,35 +1,69 @@ // define all versioned plugins here and apply in subprojects as necessary without version plugins { id 'com.github.sherter.google-java-format' version '0.9' apply false - id 'net.ltgt.apt' version '0.21' apply false - id 'net.ltgt.errorprone' version '2.0.2' apply false + id 'net.ltgt.errorprone' version '3.1.0' apply false id 'net.researchgate.release' version '2.8.1' apply false - id 'com.gradle.plugin-publish' version '0.16.0' apply false + id 'com.gradle.plugin-publish' version '1.2.0' apply false id 'io.freefair.maven-plugin' version '5.3.3.3' apply false - // apply so we can correctly configure the test runner to be gradle at the project level - id 'org.jetbrains.gradle.plugin.idea-ext' version '1.1' - // apply so that we can collect quality metrics at the root project level - id 'org.sonarqube' version '3.3' + id 'org.sonarqube' version '4.0.0.2929' } -// run tests in intellij using gradle test runner -idea.project.settings { - delegateActions { - delegateBuildRunToGradle = false - testRunner = 'GRADLE' - } -} +/* PROJECT DEPENDENCY VERSIONS */ +// define all common versioned dependencies here +project.ext.dependencyStrings = [ + // For Google libraries, check the following boms for best compatibility. + // - https://github.com/googleapis/java-shared-dependencies + // - https://github.com/googleapis/java-cloud-bom + GOOGLE_HTTP_CLIENT: 'com.google.http-client:google-http-client:1.42.2', + GOOGLE_HTTP_CLIENT_APACHE_V2: 'com.google.http-client:google-http-client-apache-v2:1.42.2', + GOOGLE_AUTH_LIBRARY_OAUTH2_HTTP: 'com.google.auth:google-auth-library-oauth2-http:1.10.0', + GUAVA: 'com.google.guava:guava:32.1.2-jre', + JSR305: 'com.google.code.findbugs:jsr305:3.0.2', // transitively pulled in by GUAVA + + // for Build Plan and Jib Plugins Extension API + BUILD_PLAN: 'com.google.cloud.tools:jib-build-plan:0.4.0', + EXTENSION_COMMON: 'com.google.cloud.tools:jib-plugins-extension-common:0.2.0', + GRADLE_EXTENSION: 'com.google.cloud.tools:jib-gradle-plugin-extension-api:0.4.0', + MAVEN_EXTENSION: 'com.google.cloud.tools:jib-maven-plugin-extension-api:0.4.0', + + COMMONS_COMPRESS: 'org.apache.commons:commons-compress:1.26.0', + ZSTD_JNI: 'com.github.luben:zstd-jni:1.5.5-5', + COMMONS_TEXT: 'org.apache.commons:commons-text:1.10.0', + JACKSON_BOM: 'com.fasterxml.jackson:jackson-bom:2.15.2', + JACKSON_DATABIND: 'com.fasterxml.jackson.core:jackson-databind', + JACKSON_DATAFORMAT_YAML: 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml', + JACKSON_DATATYPE_JSR310: 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310', + ASM: 'org.ow2.asm:asm:9.5', + PICOCLI: 'info.picocli:picocli:4.7.4', + + MAVEN_API: 'org.apache.maven:maven-plugin-api:3.9.3', + MAVEN_CORE: 'org.apache.maven:maven-core:3.9.3', + MAVEN_COMPAT: 'org.apache.maven:maven-compat:3.9.6', + MAVEN_PLUGIN_ANNOTATIONS: 'org.apache.maven.plugin-tools:maven-plugin-annotations:3.9.0', + + //test + TRUTH: 'com.google.truth:truth:1.1.5', + TRUTH8: 'com.google.truth.extensions:truth-java8-extension:1.1.5', // should match TRUTH version + JUNIT: 'junit:junit:4.13.2', + JUNIT_PARAMS: 'pl.pragmatists:JUnitParams:1.1.1', + MAVEN_TESTING_HARNESS: 'org.apache.maven.plugin-testing:maven-plugin-testing-harness:3.3.0', + MAVEN_VERIFIER: 'org.apache.maven.shared:maven-verifier:1.8.0', + MOCKITO_CORE: 'org.mockito:mockito-core:4.11.0', + SISU_PLEXUS: 'org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5', + SLF4J_API: 'org.slf4j:slf4j-api:2.0.7', + SLF4J_SIMPLE: 'org.slf4j:slf4j-simple:2.0.9', + SYSTEM_RULES: 'com.github.stefanbirkner:system-rules:1.19.0', + JBCRYPT: 'org.mindrot:jbcrypt:0.4', +] import net.ltgt.gradle.errorprone.CheckSeverity -// `java-library` must be applied before `java` +// `java-library` must be applied before `java`. // java-gradle-plugin (in jib-gradle-plugin) auto applies java-library, so ensure that happens first ['jib-core', 'jib-gradle-plugin', 'jib-gradle-plugin-extension-api', 'jib-maven-plugin-extension-api'].each { projectName -> - project(projectName) { - apply plugin: 'java-library' - } + project(projectName).apply plugin: 'java-library' } subprojects { @@ -42,62 +76,25 @@ subprojects { apply plugin: 'java' apply plugin: 'checkstyle' apply plugin: 'com.github.sherter.google-java-format' - apply plugin: 'net.ltgt.apt' apply plugin: 'net.ltgt.errorprone' apply plugin: 'jacoco' + // Guava update breaks unit tests. Workaround mentioned in https://github.com/google/guava/issues/6612#issuecomment-1614992368. + sourceSets.all { + configurations.getByName(runtimeClasspathConfigurationName) { + attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") + } + configurations.getByName(compileClasspathConfigurationName) { + attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm") + } + } + sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 compileJava.options.encoding = 'UTF-8' compileJava.options.compilerArgs += [ '-Xlint:deprecation' ] compileTestJava.options.compilerArgs += [ '-Xlint:deprecation' ] - /* PROJECT DEPENDENCY VERSIONS */ - // define all common versioned dependencies here - project.ext.dependencyStrings = [ - // For Google libraries, check the following boms for best compatibility. - // - https://github.com/googleapis/java-shared-dependencies - // - https://github.com/googleapis/java-cloud-bom - GOOGLE_HTTP_CLIENT: 'com.google.http-client:google-http-client:1.34.0', - GOOGLE_HTTP_CLIENT_APACHE_V2: 'com.google.http-client:google-http-client-apache-v2:1.34.0', - GOOGLE_AUTH_LIBRARY_OAUTH2_HTTP: 'com.google.auth:google-auth-library-oauth2-http:0.18.0', - GUAVA: 'com.google.guava:guava:31.0.1-jre', - JSR305: 'com.google.code.findbugs:jsr305:3.0.2', // transitively pulled in by GUAVA - - // for Build Plan and Jib Plugins Extension API - BUILD_PLAN: 'com.google.cloud.tools:jib-build-plan:0.4.0', - EXTENSION_COMMON: 'com.google.cloud.tools:jib-plugins-extension-common:0.2.0', - GRADLE_EXTENSION: 'com.google.cloud.tools:jib-gradle-plugin-extension-api:0.4.0', - MAVEN_EXTENSION: 'com.google.cloud.tools:jib-maven-plugin-extension-api:0.4.0', - - COMMONS_COMPRESS: 'org.apache.commons:commons-compress:1.21', - COMMONS_TEXT: 'org.apache.commons:commons-text:1.9', - JACKSON_DATABIND: 'com.fasterxml.jackson.core:jackson-databind:2.12.5', - JACKSON_DATAFORMAT_YAML: 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.12.5', - JACKSON_DATATYPE_JSR310: 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.0', - ASM: 'org.ow2.asm:asm:9.2', - PICOCLI: 'info.picocli:picocli:4.6.1', - - MAVEN_API: 'org.apache.maven:maven-plugin-api:3.8.3', - MAVEN_CORE: 'org.apache.maven:maven-core:3.8.3', - MAVEN_COMPAT: 'org.apache.maven:maven-compat:3.8.3', - MAVEN_PLUGIN_ANNOTATIONS: 'org.apache.maven.plugin-tools:maven-plugin-annotations:3.6.1', - - //test - TRUTH: 'com.google.truth:truth:1.1.3', - TRUTH8: 'com.google.truth.extensions:truth-java8-extension:1.1.3', // should match TRUTH version - JUNIT: 'junit:junit:4.13.2', - JUNIT_PARAMS: 'pl.pragmatists:JUnitParams:1.1.1', - MAVEN_TESTING_HARNESS: 'org.apache.maven.plugin-testing:maven-plugin-testing-harness:3.3.0', - MAVEN_VERIFIER: 'org.apache.maven.shared:maven-verifier:1.7.2', - MOCKITO_CORE: 'org.mockito:mockito-core:3.12.4', - SISU_PLEXUS: 'org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5', - SLF4J_API: 'org.slf4j:slf4j-api:1.7.30', - SLF4J_SIMPLE: 'org.slf4j:slf4j-simple:1.7.32', - SYSTEM_RULES: 'com.github.stefanbirkner:system-rules:1.19.0', - JBCRYPT: 'org.mindrot:jbcrypt:0.4', - ] - // Use this to ensure we correctly override transitive dependencies // TODO: There might be a plugin that does this task ensureTransitiveDependencyOverrides { @@ -121,8 +118,8 @@ subprojects { /* ERROR PRONE */ dependencies { // NullAway errorprone plugin - annotationProcessor 'com.uber.nullaway:nullaway:0.9.2' - errorprone 'com.google.errorprone:error_prone_core:2.9.0' + annotationProcessor 'com.uber.nullaway:nullaway:0.10.7' + errorprone 'com.google.errorprone:error_prone_core:2.10.0' // Using github.com/google/error-prone-javac is required when running on // JDK 8. Remove when migrating to JDK 11. if (System.getProperty('java.version').startsWith('1.8.')) { @@ -179,8 +176,8 @@ subprojects { /* CHECKSTYLE */ /* TEST CONFIG */ - tasks.withType(Test) { - reports.html.setDestination file("${reporting.baseDir}/${name}") + tasks.withType(Test).configureEach { + reports.html.outputLocation = file("${reporting.baseDir}/${name}") } test { @@ -193,7 +190,7 @@ subprojects { // testCompile project(path:':project-name', configuration:'tests') task testJar(type: Jar) { from sourceSets.test.output.classesDirs - classifier = 'tests' + archiveClassifier = 'tests' } // to import resources do: sourceSets.test.resources.srcDirs project(':project-name').sourceSets.test.resources @@ -211,6 +208,8 @@ subprojects { integrationTest { java.srcDir file('src/integration-test/java') resources.srcDir file('src/integration-test/resources') + compileClasspath += sourceSets.main.output + sourceSets.test.output + runtimeClasspath += sourceSets.main.output + sourceSets.test.output } } @@ -220,15 +219,6 @@ subprojects { integrationTestRuntime.extendsFrom testRuntime } - dependencies { - integrationTestImplementation sourceSets.main.output - integrationTestImplementation sourceSets.test.output - integrationTestImplementation configurations.compile - integrationTestImplementation configurations.testImplementation - integrationTestImplementation configurations.runtime - integrationTestImplementation configurations.testRuntime - } - // Integration tests must be run explicitly task integrationTest(type: Test) { testClassesDirs = sourceSets.integrationTest.output.classesDirs @@ -239,7 +229,7 @@ subprojects { task integrationTestJar(type: Jar) { from sourceSets.integrationTest.output.classesDirs - classifier = 'integration-tests' + archiveClassifier = 'integration-tests' } configurations { @@ -277,6 +267,14 @@ subprojects { 'Built-Gradle': gradle.gradleVersion } } + normalization { + runtimeClasspath { + metaInf { + ignoreAttribute("Built-By") + ignoreAttribute("Built-Date") + } + } + } /* JAR */ /* MAVEN CENTRAL RELEASES */ @@ -285,12 +283,12 @@ subprojects { apply plugin: 'maven-publish' task sourceJar(type: Jar) { from sourceSets.main.allJava - classifier 'sources' + archiveClassifier = 'sources' } task javadocJar(type: Jar, dependsOn: javadoc) { from javadoc.destinationDir - classifier 'javadoc' + archiveClassifier = 'javadoc' } publishing { @@ -373,7 +371,7 @@ subprojects { // sourceProject(Project) accepts a project and adds it as a dependency in a special manner: // 1. force evaluation of the project first // 2. add the project classes as "compileOnly" and make it available to tests in "testImplementation" - // 3. add the project's depedencies as "implementation" + // 3. add the project's dependencies as "implementation" // 4. remove any transitive reference of any sourceProject depenency that may have appeared // 5. add the project's classes to the final jar // Other nice effects (vs shadowJar) @@ -416,7 +414,7 @@ subprojects { project.afterEvaluate { project.configurations.implementation.dependencies.each { dependency -> if (dependency instanceof ProjectDependency) { - throw new GradleException("disallowed project dependency:" + dependency + ", in project:" + project); + throw new GradleException('disallowed project dependency:' + dependency + ', in project:' + project); } } } @@ -425,12 +423,12 @@ subprojects { /* TEST COVERAGE */ jacocoTestReport { reports { - xml.enabled true - html.enabled false + xml.required = true + html.required = false } } /* TEST COVERAGE */ - + /* INCLUDED PROJECT DEPENDENCY HELPER */ /* LOCAL DEVELOPMENT HELPER TASKS */ diff --git a/config/checkstyle/copyright-java.header b/config/checkstyle/copyright-java.header index 10f176ef90..e60211cae3 100644 --- a/config/checkstyle/copyright-java.header +++ b/config/checkstyle/copyright-java.header @@ -1,5 +1,5 @@ ^/\*$ -^ \* Copyright 20(17|18|19|20|21) Google LLC\.$ +^ \* Copyright 20(17|18|19|20|21|22|23|24) Google LLC\.$ ^ \*$ ^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\); you may not$ ^ \* use this file except in compliance with the License\. You may obtain a copy of$ diff --git a/docs/configure-gcp-credentials.md b/docs/configure-gcp-credentials.md index 91e7dfa467..22c2b47553 100644 --- a/docs/configure-gcp-credentials.md +++ b/docs/configure-gcp-credentials.md @@ -1,6 +1,6 @@ # Configuring Credentials for [Google Container Registry (GCR)](https://cloud.google.com/container-registry/) -There are a few ways of supplying Jib with the credentials to push and pull images from your private GCR regsitry. +There are a few ways of supplying Jib with the credentials to push and pull images from your private GCR registry. ## Using the Docker credential helper diff --git a/docs/default_base_image.md b/docs/default_base_image.md index 06c6d92de0..4d98a4c57b 100644 --- a/docs/default_base_image.md +++ b/docs/default_base_image.md @@ -1,12 +1,12 @@ # Default Base Images in Jib -## Jib Build Plugins 3.0 +## Jib Build Plugins 3+ -Starting from version 3.0, the default base image is the official [`adoptopenjdk`](https://hub.docker.com/_/adoptopenjdk) image on Docker Hub. AdoptOpenJDK (which is [being renamed to Adoptium](https://blog.adoptopenjdk.net/2020/06/adoptopenjdk-to-join-the-eclipse-foundation/)) is a popular OpenJDK build also used by [Google Cloud Buildpacks](https://github.com/GoogleCloudPlatform/buildpacks) (for Java) by default. +Starting 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.) For WAR projects, the default is the official [`jetty`](https://hub.docker.com/_/jetty) image on Docker Hub. -Note that Jib's default choice for 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 [``](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. +Note 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 [``](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. ### Docker Hub Download Rate Limit @@ -28,9 +28,9 @@ Note that, even after Jib fully cached a base image, Jib still connects to Docke Some options: * Configure a registry mirror. * Prevent Jib from accessing Docker Hub (after Jib cached a base image locally). - - Pin to a specific base image using a SHA digest (for example, `jib.from.image='adoptopenjdk:11-jre@sha256:...'`). + - Pin to a specific base image using a SHA digest (for example, `jib.from.image='eclipse-temurin:11-jre@sha256:...'`). - Do offline building. - - Read a base from a local Docker deamon. + - Read a base from a local Docker daemon. - Set up a local registry, store a base image, and read it from the local registry. * Retry with increasing backoffs. @@ -81,4 +81,4 @@ For some reason if you have to keep the exact same behavior when using 3.0, you ## Jib CLI -The JAR mode of the Jib CLI has always used AdoptOpenJDK and the WAR mode uses `jetty`. +For 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`. diff --git a/docs/faq.md b/docs/faq.md index 51a0409668..273854ead9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -14,7 +14,7 @@ If a question you have is not answered below, please [submit an issue](/../../is [Why is my image created 48+ years ago?](#why-is-my-image-created-48-years-ago)\ [Where is the application in the container filesystem?](#where-is-the-application-in-the-container-filesystem)\ [How are Jib applications layered?](#how-are-jib-applications-layered)\ -[Can I learn more about container images?](#can-i-learn-more-about-container-images) +[Can I learn more about container images?](#can-i-learn-more-about-container-images)\ [Which base image (JDK) does Jib use?](#which-base-image-jdk-does-jib-use) **How-Tos**\ @@ -42,11 +42,13 @@ If a question you have is not answered below, please [submit an issue](/../../is [What should I do when the registry responds with UNAUTHORIZED?](#what-should-i-do-when-the-registry-responds-with-unauthorized)\ [How do I configure a proxy?](#how-do-i-configure-a-proxy)\ [How can I examine network traffic?](#how-can-i-examine-network-traffic)\ -[How do I view debug logs for Jib?](#how-do-i-view-debug-logs-for-jib) +[How do I view debug logs for Jib?](#how-do-i-view-debug-logs-for-jib)\ +[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)\ +[I am seeing `Unsupported class file major version` when building.](#i-am-seeing-unsupported-class-file-major-version-when-building)\ +[I am seeing `NoClassDefFoundError: com/github/luben/zstd/ZstdOutputStream` when building.](#i-am-seeing-noclassdeffounderror-comgithublubenzstdzstdoutputstream-when-building) **Launch Problems**\ [I am seeing `ImagePullBackoff` on my pods.](#i-am-seeing-imagepullbackoff-on-my-pods-in-minikube)\ -[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)\ [Why won't my container start?](#why-wont-my-container-start) **Jib CLI**\ @@ -88,9 +90,9 @@ For more information, see [steps 4-6 of the Kubernetes Engine deployment tutoria ### Where is bash? -By 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`). Starting from Jib build plugins 3.0, the default base image is [`adoptopenjdk`](default_base_image.md) (and [`jetty`](https://hub.docker.com/_/jetty) for WAR projects), which contains shell programs. +By 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. -Note that you can always set a different base image. Jib's default choice for 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. +Note 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. * Configuring a base image in Maven ```xml @@ -184,7 +186,7 @@ Jib applications are split into the following layers: ### Which base image (JDK) does Jib use? -[`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. +[`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. ### Can I learn more about container images? @@ -213,6 +215,9 @@ spec: - name: JAVA_TOOL_OPTIONS value: ``` +Note 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. + +For 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`. #### Other Environment Variables @@ -357,7 +362,7 @@ mvn compile resources:copy-resources jib:build The same can be accomplished in Gradle by using a `Copy` task. In your `build.gradle`: ```groovy -jib.extraDirectories = file('build/extra-directory') +jib.extraDirectories.paths = ['build/extra-directory'] task setupExtraDir(type: Copy) { from file('build/generated/files') @@ -398,7 +403,7 @@ Beware: in Java 8 and earlier, specifying only a port meant that the JDWP socket ### I would like to run my application with a javaagent. -You can run your container with a javaagent by placing it somewhere in the `src/main/jib` directory to add it to the container's filesystem, then pointing to it using Jib's `container.jvmFlags` configuration. +You 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. #### Maven @@ -461,10 +466,11 @@ jib.to.image = 'gcr.io/my-gcp-project/my-app:' + System.nanoTime() A Dockerfile that performs a Jib-like build is shown below: ```Dockerfile -# Jib uses AdoptOpenJDK as the default base image -FROM adoptopenjdk:11-jre +# Jib uses Adoptium Eclipse Temurin (formerly AdoptOpenJDK). +FROM eclipse-temurin:11-jre -# Multiple copy statements are used to break the app into layers, allowing for faster rebuilds after small changes +# Multiple copy statements are used to break the app into layers, +# allowing for faster rebuilds after small changes COPY dependencyJars /app/libs COPY snapshotDependencyJars /app/libs COPY projectDependencyJars /app/libs @@ -489,7 +495,7 @@ To inspect the image that is produced from the build using Docker, you can use c ### How do I specify a platform in the manifest list (or OCI index) of a base image? -Newer Jib verisons added an _incubating feature_ that provides support for selecting base images with the desired platforms from a manifest list. For example, +Newer Jib versions added an _incubating feature_ that provides support for selecting base images with the desired platforms from a manifest list. For example, ```xml @@ -569,14 +575,14 @@ The Jib build plugins have an extension framework that enables anyone to easily See 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). -Starting from Jib build plugins 3.0, [the default base image is `adoptopenjdk` and `jetty` 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. +Starting 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. Some other alternatives to get around the rate limits: * Prevent Jib from accessing Docker Hub (after Jib cached a base image locally). - - **Pin to a specific base image using a SHA digest (for example, `jib.from.image='adoptopenjdk: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 the Docker Hub. See [this Stack Overflow answer](https://stackoverflow.com/a/61190005/1701388) for more details. + - **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. - (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. - - **Read a base from a local Docker deamon.** Store an image to your local Docker daemon, and set, say, `jib.from.image='docker://adoptopenjdk: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. + - **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. - **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. * Retry with increasing backoffs. For example, using the [`retry`](https://github.com/kadwanev/retry) tool. @@ -609,21 +615,40 @@ Depending on registry implementations, it is also possible that the registry act If the registry returns `401 Unauthorized` or `"code":"UNAUTHORIZED"`, it is often due to credential misconfiguration. Examples: -* You did not configure auth information in the default places where Jib searches. - - `$HOME/.docker/config.json`, [one of the configuration files](https://docs.docker.com/engine/reference/commandline/cli/#configuration-files) for the `docker` 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`). - - (Starting from Jib 2.2.0) You can set the environment variable `$DOCKER_CONFIG` for the _directory_ containing Docker configuration files. `$DOCKER_CONFIG/config.json` takes precedence over `$HOME/.docker/config.json`. - - Some common credential helpers on `$PATH` (for example, `docker-credential-osxkeychain`, `docker-credential-ecr-login`, etc.) for well-known registries. +* 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)). + + - Docker credential file (as generated by `docker login` or `podman login`) at + - `$XDG_RUNTIME_DIR/containers/auth.json`, `$XDG_CONFIG_HOME/containers/auth.json` or `$HOME/.config/containers/auth.json` + - `$DOCKER_CONFIG/config.json` + - `$HOME/.docker/config.json` + + 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`). + + 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. + - Jib configurations + - Configuring credential helpers: [``](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) - Specific credentials (not recommend): [`/`](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) - 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) + +* For Google Cloud Registry (GCR), the Container Registry API is not yet enabled for your project. + - 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` + * `$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. + * Different auth configurations exist in multiple places, and Jib is not picking up the auth information you are working on. + * 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. + * Configured credentials have access to the base image repository but not to the target image repository (or vice versa). + * Typos in username, password, image names, repository names, or registry names. This is a very common error. + * 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. + * Incorrect port number in image references (`registry.hostname:/...`). + * 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). Note, if Jib was able to retrieve credentials, you should see a log message like these: @@ -671,12 +696,12 @@ mvn --batch-mode -Djava.util.logging.config.file=path/to/logging.properties -Dji ``` or ```sh -gradle --no-daemon --console=plain -Djava.util.logging.config.file=path/to/logging.properties -Djib.serialize=true ... +gradle --no-daemon --console=plain --info -Djava.util.logging.config.file=path/to/logging.properties -Djib.serialize=true ... ``` **Note**: Jib Gradle plugins prior to version 2.2.0 have an issue generating HTTP logs ([#2356](https://github.com/GoogleContainerTools/jib/issues/2356)). -You may wish to enable the debug logs too (`-X` for Maven, or `--debug --stacktrace` for Gradle). +You may want to enable the debug logs too (`-X` for Maven, or `--debug --stacktrace` for Gradle). When configured correctly, you should see logs like this: ``` @@ -699,13 +724,59 @@ Accept-Encoding: gzip Authorization: User-Agent: jib 2.1.1-SNAPSHOT jib-maven-plugin Google-HTTP-Java-Client/1.34.0 (gzip) ``` - + ### How do I view debug logs for Jib? Maven: use `mvn -X -Djib.serialize=true` to enable more detailed logging and serialize Jib's actions. Gradle: use `gradle --debug -Djib.serialize=true` to enable more detailed logging and serialize Jib's actions. +### I am seeing `Method Not Found` or `Class Not Found` errors when building. + +Sometimes 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). + +This 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. + +`build.gradle` (root) +```groovy +plugins { + id 'com.google.cloud.tools.jib' version 'x.y.z' apply false +} +``` + +`build.gradle` (sub-project) +```groovy +plugins { + id 'com.google.cloud.tools.jib' +} +``` + +### I am seeing `Unsupported class file major version` when building. + +When 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): + +``` +Failed 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 +``` + +Jib 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. + +**Workaround**: to prevent Jib from doing auto-inference, you can manually set your desired main class via [``](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#container-object) (for example, `com.example.your.Main`). As with other Jib parameters, it can be set through system/Maven properties or on the command-line (for example, `-Dcontainer.mainClass=...`). + +Note 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. + +### I am seeing `NoClassDefFoundError: com/github/luben/zstd/ZstdOutputStream` when building. + +Jib supports base image layers with media-type `application/vnd.oci.image.layer.v1.tar+zstd`, i.e. compressed with zstd algorithm instead of gzip. + +However, the dependency to zstd is optional, so pulling such layers will result in: + +``` +java.lang.NoClassDefFoundError: com/github/luben/zstd/ZstdOutputStream +at org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream. +``` + +This can be solved by adding a dependency to artifact `com.github.luben:zstd-jni:1.5.2-3` to the plugin. ## Launch problems @@ -739,35 +810,15 @@ kubectl patch serviceaccount default \ See more at [Using Google Container Registry (GCR) with Minikube](https://ryaneschinger.com/blog/using-google-container-registry-gcr-with-minikube/). -### I am seeing `Method Not Found` or `Class Not Found` errors when building. - -Sometimes 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). - -This 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. - -`build.gradle` (root) -```groovy -plugins { - id 'com.google.cloud.tools.jib' version 'x.y.z' apply false -} -``` - -`build.gradle` (sub-project) -```groovy -plugins { - id 'com.google.cloud.tools.jib' -} -``` - ### Why won't my container start? There are some common reasons why containers fail on launch. -#### My shell script won't run - +#### My shell script won't run. + Jib 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. -#### The container fails with `exec` errors +#### The container fails with `exec` errors. A Jib user reported an error launching their container: ``` @@ -783,6 +834,7 @@ Solution: The user installed the file in a different location. ## Jib CLI ### How does the `jar` command support Standard JARs? + The 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. The 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. @@ -790,6 +842,7 @@ The current limitation of using a fat JAR is that the embedded dependencies will A standard JAR can be containerized by the `jar` command in two modes, exploded or packaged. #### Exploded Mode (Recommended) + Achieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar` The default mode for containerizing a JAR. It will open up the JAR and optimally place files into the following layers: @@ -802,6 +855,7 @@ The default mode for containerizing a JAR. It will open up the JAR and optimally **Entrypoint** : `java -cp /app/dependencies/:/app/explodedJar/ ${MAIN_CLASS}` #### Packaged Mode + Achieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar --mode packaged`. It will result in the following layers on the container: @@ -812,10 +866,12 @@ It will result in the following layers on the container: **Entrypoint** : `java -jar ${JAR_NAME}.jar` ### How does the `jar` command support Spring Boot JARs? + The `jar` command currently supports containerization of Spring Boot fat JARs. A Spring-Boot fat JAR can be containerized in two modes, exploded or packaged. #### Exploded Mode (Recommended) + Achieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar` The 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: @@ -829,6 +885,7 @@ The default mode for containerizing a JAR. It will respect [`layers.idx`](https: **Entrypoint** : `java -cp /app org.springframework.boot.loader.JarLauncher` #### Packaged Mode + Achieved by calling `jib jar --target ${TARGET_REGISTRY} ${JAR_NAME}.jar --mode packaged` It will containerize the JAR as is. However, **note** that we highly recommend against using packaged mode for containerizing Spring Boot fat JARs. @@ -836,6 +893,7 @@ It will containerize the JAR as is. However, **note** that we highly recommend a **Entrypoint**: `java -jar ${JAR_NAME}.jar` ### How does the `war` command work? + The `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: * Other Dependencies Layer @@ -843,7 +901,7 @@ The `war` command currently supports containerization of standard WARs. It uses * Resources Layer * Classes Layer -The default entrypoint when using a jetty base image will be `java -jar /usr/local/jetty/start.jar` unless you choose to specify a custom one. +The 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. You 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: ``` diff --git a/docs/google-cloud-build.md b/docs/google-cloud-build.md new file mode 100644 index 0000000000..71d779bdaa --- /dev/null +++ b/docs/google-cloud-build.md @@ -0,0 +1,33 @@ +# Jib on Google Cloud Build + +You can use Jib on [Google Cloud Build](https://cloud.google.com/build) in a simple step: + +```yaml +steps: + - name: 'gcr.io/cloud-builders/javac:8' + entrypoint: './gradlew' + args: ['--console=plain', '--no-daemon', ':server:jib', '-Djib.to.image=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA'] +``` + +Any 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: + +```yaml +steps: + - name: 'docker.io/library/eclipse-temurin:21' + entrypoint: './gradlew' + args: ['--console=plain', '--no-daemon', ':server:jib', '-Djib.to.image=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA'] +``` + +To 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: + +```yaml +steps: + - name: 'gcr.io/distroless/java17-debian11:debug' + entrypoint: '/busybox/sh' + args: + - -c + - | + ln -s /busybox/sh /bin/sh + ln -s /busybox/env /usr/bin/env + /workspace/gradlew --console=plain --no-daemon --gradle-user-home=/home/.gradle :server:jib -Djib.to.image=gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA +``` diff --git a/docs/privacy.md b/docs/privacy.md index 28a81303a5..c0c78b3dc8 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -11,7 +11,7 @@ and version. ### How to disable update checks 1. set the `jib.disableUpdateChecks` system property to `true` -2. set `disableUpdateChecks` to `true` in Jib's global config. The global config is in the following locations by default: +2. set `disableUpdateCheck` to `true` in Jib's global config. The global config is in the following locations by default: * 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` * Mac: `$XDG_CONFIG_HOME/Google/Jib/config.json` (if `$XDG_CONFIG_HOME` is defined), else `$HOME/Library/Preferences/Google/Jib/config.json` * Windows: `$XDG_CONFIG_HOME\Google\Jib\Config\config.json` (if `$XDG_CONFIG_HOME` is defined), else `%LOCALAPPDATA%\Google\Jib\Config\config.json` \ No newline at end of file diff --git a/examples/dropwizard/pom.xml b/examples/dropwizard/pom.xml index 82e57b5978..ba520eec2d 100644 --- a/examples/dropwizard/pom.xml +++ b/examples/dropwizard/pom.xml @@ -26,7 +26,7 @@ 1.5.0 /app - 3.1.4 + 3.4.4 diff --git a/examples/helloworld/build.gradle b/examples/helloworld/build.gradle index 2184fdfc3b..6de1c83cd2 100644 --- a/examples/helloworld/build.gradle +++ b/examples/helloworld/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'com.google.cloud.tools.jib' version '3.1.4' + id 'com.google.cloud.tools.jib' version '3.4.4' } sourceCompatibility = 1.8 @@ -11,7 +11,7 @@ repositories { } dependencies { - compile 'com.google.guava:guava:23.6-jre' + implementation 'com.google.guava:guava:23.6-jre' } jib.to.image = 'gcr.io/REPLACE-WITH-YOUR-GCP-PROJECT/image-built-with-jib' diff --git a/examples/helloworld/pom.xml b/examples/helloworld/pom.xml index 26643b53a9..5f0a3ceb1c 100644 --- a/examples/helloworld/pom.xml +++ b/examples/helloworld/pom.xml @@ -9,7 +9,7 @@ UTF-8 - 3.1.4 + 3.4.4 3.8.0 @@ -17,7 +17,7 @@ com.google.guava guava - 30.0-jre + 32.0.0-jre diff --git a/examples/java-agent/build.gradle b/examples/java-agent/build.gradle index 621fad16c4..87157b9b4f 100644 --- a/examples/java-agent/build.gradle +++ b/examples/java-agent/build.gradle @@ -1,8 +1,8 @@ plugins { id 'java' - id 'com.google.cloud.tools.jib' version '3.1.4' + id 'com.google.cloud.tools.jib' version '3.4.4' id 'de.undercouch.download' version '4.0.0' - id "com.gorylenko.gradle-git-properties" version "2.2.0" + id 'com.gorylenko.gradle-git-properties' version '2.2.0' } ext { @@ -24,8 +24,8 @@ repositories { } dependencies { - compile "com.sparkjava:spark-core:2.9.1" - compile "org.slf4j:slf4j-simple:1.7.28" + implementation 'com.sparkjava:spark-core:2.9.1' + implementation 'org.slf4j:slf4j-simple:1.7.28' } // Download and extract the Cloud Debugger Java Agent diff --git a/examples/java-agent/pom.xml b/examples/java-agent/pom.xml index 1d05059f05..8a12ede648 100644 --- a/examples/java-agent/pom.xml +++ b/examples/java-agent/pom.xml @@ -9,7 +9,7 @@ UTF-8 - 3.1.4 + 3.4.4 3.8.0 1.4.2 3.0.1 diff --git a/examples/ktor/build.gradle.kts b/examples/ktor/build.gradle.kts index ca4a65d11f..7ff8375c0a 100644 --- a/examples/ktor/build.gradle.kts +++ b/examples/ktor/build.gradle.kts @@ -1,7 +1,7 @@ plugins { application kotlin("jvm") version "1.3.10" - id("com.google.cloud.tools.jib") version "3.1.4" + id("com.google.cloud.tools.jib") version "3.4.4" } group = "example" diff --git a/examples/micronaut/build.gradle b/examples/micronaut/build.gradle index 028bcea83b..689734851d 100644 --- a/examples/micronaut/build.gradle +++ b/examples/micronaut/build.gradle @@ -2,7 +2,7 @@ plugins { id "groovy" id "com.github.johnrengelman.shadow" version "5.2.0" id "application" - id 'com.google.cloud.tools.jib' version '3.1.4' + id 'com.google.cloud.tools.jib' version '3.4.4' } version "0.1" diff --git a/examples/multi-module/README.md b/examples/multi-module/README.md index dc2248f12c..e47a5e9727 100644 --- a/examples/multi-module/README.md +++ b/examples/multi-module/README.md @@ -27,7 +27,7 @@ in gradle to achieve this. This configuration can be seen in the Care must be taken when adding custom attributes to a `MANIFEST.MF`. Attributes whose values change on every build can affect reproducibility even -with the modifications outlined aboved. +with the modifications outlined above. # How to run diff --git a/examples/multi-module/build.gradle b/examples/multi-module/build.gradle index bcb0a93df7..0684767f76 100644 --- a/examples/multi-module/build.gradle +++ b/examples/multi-module/build.gradle @@ -2,5 +2,5 @@ plugins { id 'org.springframework.boot' version '2.0.3.RELEASE' apply false id 'io.spring.dependency-management' version '1.0.6.RELEASE' apply false - id 'com.google.cloud.tools.jib' version '3.1.4' apply false + id 'com.google.cloud.tools.jib' version '3.4.4' apply false } diff --git a/examples/multi-module/name-service/build.gradle b/examples/multi-module/name-service/build.gradle index 3a10607627..977be7685b 100644 --- a/examples/multi-module/name-service/build.gradle +++ b/examples/multi-module/name-service/build.gradle @@ -16,7 +16,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - implementation project(":shared-library") + implementation project(':shared-library') } // IMPORTANT: Set the environment variable PROJECT_ID to your own Google Cloud Platform project. diff --git a/examples/multi-module/pom.xml b/examples/multi-module/pom.xml index cd334db9cd..3a3b444285 100644 --- a/examples/multi-module/pom.xml +++ b/examples/multi-module/pom.xml @@ -41,7 +41,7 @@ com.google.cloud.tools jib-maven-plugin - 3.1.4 + 3.4.4 diff --git a/examples/spring-boot/build.gradle b/examples/spring-boot/build.gradle index 9b911926c3..709dc8cd16 100644 --- a/examples/spring-boot/build.gradle +++ b/examples/spring-boot/build.gradle @@ -4,7 +4,7 @@ plugins { id 'idea' id 'org.springframework.boot' version '2.1.6.RELEASE' id 'io.spring.dependency-management' version '1.0.6.RELEASE' - id 'com.google.cloud.tools.jib' version '3.1.4' + id 'com.google.cloud.tools.jib' version '3.4.4' } repositories { @@ -15,5 +15,5 @@ sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { - compile('org.springframework.boot:spring-boot-starter-web') + implementation 'org.springframework.boot:spring-boot-starter-web' } diff --git a/examples/spring-boot/pom.xml b/examples/spring-boot/pom.xml index 0a76a6b68d..5f289994a7 100644 --- a/examples/spring-boot/pom.xml +++ b/examples/spring-boot/pom.xml @@ -29,7 +29,7 @@ com.google.cloud.tools jib-maven-plugin - 3.1.4 + 3.4.4 diff --git a/examples/vertx/build.gradle b/examples/vertx/build.gradle index 21500abf80..0e0108958b 100644 --- a/examples/vertx/build.gradle +++ b/examples/vertx/build.gradle @@ -1,6 +1,6 @@ plugins { id 'io.vertx.vertx-plugin' version '0.1.0' - id 'com.google.cloud.tools.jib' version '3.1.4' + id 'com.google.cloud.tools.jib' version '3.4.4' } repositories { diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..7fe7ea26ee --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1024m +org.gradle.caching=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94920145f3..ec991f9aa1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jib-cli/CHANGELOG.md b/jib-cli/CHANGELOG.md index 47ec75290f..aa7261fb61 100644 --- a/jib-cli/CHANGELOG.md +++ b/jib-cli/CHANGELOG.md @@ -5,14 +5,65 @@ All notable changes to this project will be documented in this file. ### Added +### Changed + +### Fixed + +## 0.13.0 + +### Added + +### Changed + +### Fixed +- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171)) +- 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)) + +## 0.12.0 + +### Changed +- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745)) +- Re-synchronized jackson dependencies with BOM to use latest versions ([#3768](https://github.com/GoogleContainerTools/jib/pull/3768)) + +## 0.11.0 + +### Added +- 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)) +- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)). +- 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)). + +### Changed +- Upgraded slf4j-api to 2.0.0 ([#3735](https://github.com/GoogleContainerTools/jib/pull/3735)). +- Upgraded nullaway to 0.9.9 ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720)). + +Thanks to our community contributors @wwadge @oliver-brm and @laurentsimon! + +## 0.10.0 + +### Changed +- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/issues/3612)). + +### Fixed + +- Incorrect release sha256 file for jib-cli. ([#3584](https://github.com/GoogleContainerTools/jib/issues/3584)) + +## 0.9.0 + +### Changed + +- 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)) + +## 0.8.0 + +### Added + - 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)) - 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)) ### Changed - 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)) - -### Fixed +- 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)) ## 0.7.0 diff --git a/jib-cli/README.md b/jib-cli/README.md index 0a166cad0c..e23985cade 100644 --- a/jib-cli/README.md +++ b/jib-cli/README.md @@ -4,6 +4,7 @@ [![Chocolatey](https://img.shields.io/chocolatey/v/jib.svg)](https://chocolatey.org/packages/jib) [![Chocolatey](https://img.shields.io/chocolatey/dt/jib.svg)](https://chocolatey.org/packages/jib) +[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev) `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). @@ -55,11 +56,21 @@ Most users should download a ZIP archive (Java application). We are working on r A JRE is required to run this Jib CLI distribution. -Find the [latest jib-cli 0.7.0 release](https://github.com/GoogleContainerTools/jib/releases/tag/v0.7.0-cli) on the [Releases page](https://github.com/GoogleContainerTools/jib/releases), download `jib-jre-.zip`, and unzip it. 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. +Find 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-.zip`. + +Unzip 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. + +We 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: +1. Install the verification tool from [slsa-framework/slsa-verifier#installation](https://github.com/slsa-framework/slsa-verifier#installation). +2. Download the signature file `jib-jre-.zip.intoto.jsonl` from the [GitHub releases page](https://github.com/GoogleContainerTools/jib/releases/latest). +3. Run the verifier: +```shell +slsa-verifier -artifact-path jib-jre-.zip -provenance jib-jre-.zip.intoto.jsonl -source github.com/GoogleContainerTools/jib -branch master -workflow-input release_version= +``` ### Windows: Install with `choco` -On Windows, you can use the [`choco`](https://community.chocolatey.org/packages/jib) command. To install, upgradle, or uninstall Jib CLI, run the following commands from the command-line or PowerShell: +On 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: ``` choco install jib choco upgrade jib @@ -179,7 +190,7 @@ This command follows the following pattern: ## Quickstart -1. 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`: +1. 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`: ``` $ jib war --target=docker://cli-war-quickstart .war ``` @@ -231,7 +242,7 @@ Credentials can be specified using credential helpers or username + password. Th ``` --credential-helper 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-` - --to-crendential-helper credential helper for communicating with target registry, either a path to the helper, or a suffix for an executable named `docker-credential- + --to-credential-helper credential helper for communicating with target registry, either a path to the helper, or a suffix for an executable named `docker-credential- --from-credential-helper credential helper for communicating with base image registry, either a path to the helper, or a suffix for an executable named `docker-credential-` --username username for communicating with both target and base image registries @@ -287,7 +298,7 @@ Some options can be set in the global Jib configuration file. The file is at the ### Properties * `disableUpdateCheck`: when set to true, disables the periodic up-to-date version check. -* `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 successfuly pull a base image. +* `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. ```json { @@ -393,7 +404,7 @@ layers: - name: "images" # second layer, inherits file properties from global files: - src: "images" - - dest: "/images" + dest: "/images" ``` #### Layers Behavior @@ -417,7 +428,7 @@ layers: - src: file.txt dest: /file.txt ``` -- Parent directories that are not exiplicitly 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. +- 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. ``` - name: layer properties: @@ -434,7 +445,7 @@ layers: #### Base Image Parameter Inheritance -Some values defined in the base image may be preserved and propogated into the new container. +Some values defined in the base image may be preserved and propagated into the new container. Parameters will append to base image value: - `volumes` diff --git a/jib-cli/build.gradle b/jib-cli/build.gradle index 75d493daa4..ee5c6ba179 100644 --- a/jib-cli/build.gradle +++ b/jib-cli/build.gradle @@ -12,7 +12,7 @@ ext { // use `installDist` or `distZip` to create an installable application application { applicationName = 'jib' - mainClassName = cliMainClass + mainClass = cliMainClass } sourceSets.main.java.srcDirs += ["${buildDir}/generated-src"] @@ -35,6 +35,7 @@ dependencies { implementation project(':jib-plugins-common') implementation dependencyStrings.COMMONS_TEXT + implementation(platform(dependencyStrings.JACKSON_BOM)) implementation dependencyStrings.JACKSON_DATAFORMAT_YAML implementation dependencyStrings.JACKSON_DATABIND implementation dependencyStrings.GUAVA diff --git a/jib-cli/gradle.properties b/jib-cli/gradle.properties index c4baa924a0..5b92d1cfd5 100644 --- a/jib-cli/gradle.properties +++ b/jib-cli/gradle.properties @@ -1 +1 @@ -version = 0.7.1-SNAPSHOT +version = 0.13.1-SNAPSHOT diff --git a/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/JarCommandTest.java b/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/JarCommandTest.java index b6aa88ede9..557b0ba703 100644 --- a/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/JarCommandTest.java +++ b/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/JarCommandTest.java @@ -19,21 +19,33 @@ import static com.google.common.truth.Truth.assertThat; import com.google.cloud.tools.jib.Command; -import com.google.cloud.tools.jib.blob.Blobs; +import com.google.cloud.tools.jib.api.HttpRequestTester; +import com.google.common.base.Preconditions; import com.google.common.io.Resources; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; import java.util.jar.Attributes; import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import javax.annotation.Nullable; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; import org.junit.After; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import picocli.CommandLine; @@ -45,6 +57,15 @@ public class JarCommandTest { @Nullable private String containerName; + @BeforeClass + public static void createJars() throws IOException, URISyntaxException { + createJarFile( + "jarWithCp.jar", "HelloWorld", "dependency1.jar directory/dependency2.jar", "HelloWorld"); + createJarFile("noDependencyJar.jar", "HelloWorld", null, "HelloWorld"); + createJarFile("dependency1.jar", "dep/A", null, null); + createJarFile("directory/dependency2.jar", "dep2/B", null, null); + } + @After public void tearDown() throws IOException, InterruptedException { if (containerName != null) { @@ -87,8 +108,17 @@ public void testStandardJar_explodedMode_toDocker() Path jarPath = Paths.get(Resources.getResource("jarTest/standard/jarWithCp.jar").toURI()); Integer exitCode = new CommandLine(new JibCli()) - .execute("jar", "--target", "docker://exploded-jar", jarPath.toString()); - String output = new Command("docker", "run", "--rm", "exploded-jar").run(); + .execute( + "jar", + "--from", + "eclipse-temurin:8-jdk-focal", + "--target", + "docker://exploded-jar", + jarPath.toString()); + String output = + new Command("docker", "run", "--rm", "exploded-jar", "--privileged", "--network=host") + .run(); + try (JarFile jarFile = new JarFile(jarPath.toFile())) { String classPath = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH); @@ -105,8 +135,17 @@ public void testNoDependencyStandardJar_explodedMode_toDocker() Path jarPath = Paths.get(Resources.getResource("jarTest/standard/noDependencyJar.jar").toURI()); Integer exitCode = new CommandLine(new JibCli()) - .execute("jar", "--target", "docker://exploded-no-dep-jar", jarPath.toString()); - String output = new Command("docker", "run", "--rm", "exploded-no-dep-jar").run(); + .execute( + "jar", + "--from", + "eclipse-temurin:8-jdk-focal", + "--target", + "docker://exploded-no-dep-jar", + jarPath.toString()); + String output = + new Command( + "docker", "run", "--rm", "exploded-no-dep-jar", "--privileged", "--network=host") + .run(); try (JarFile jarFile = new JarFile(jarPath.toFile())) { String classPath = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH); @@ -124,8 +163,17 @@ public void testStandardJar_packagedMode_toDocker() Integer exitCode = new CommandLine(new JibCli()) .execute( - "jar", "--target", "docker://packaged-jar", jarPath.toString(), "--mode=packaged"); - String output = new Command("docker", "run", "--rm", "packaged-jar").run(); + "jar", + "--from", + "eclipse-temurin:8-jdk-focal", + "--target", + "docker://packaged-jar", + jarPath.toString(), + "--mode=packaged"); + String output = + new Command("docker", "run", "--rm", "packaged-jar", "--privileged", "--network=host") + .run(); + try (JarFile jarFile = new JarFile(jarPath.toFile())) { String classPath = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH); @@ -144,11 +192,16 @@ public void testNoDependencyStandardJar_packagedMode_toDocker() new CommandLine(new JibCli()) .execute( "jar", + "--from", + "eclipse-temurin:8-jdk-focal", "--target", "docker://packaged-no-dep-jar", jarPath.toString(), "--mode=packaged"); - String output = new Command("docker", "run", "--rm", "packaged-no-dep-jar").run(); + String output = + new Command( + "docker", "run", "--rm", "packaged-no-dep-jar", "--privileged", "--network=host") + .run(); try (JarFile jarFile = new JarFile(jarPath.toFile())) { String classPath = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH); @@ -167,17 +220,33 @@ public void testSpringBootLayeredJar_explodedMode() throws IOException, Interrup Integer exitCode = new CommandLine(new JibCli()) - .execute("jar", "--target", "docker://spring-boot-jar-layered", jarPath.toString()); + .execute( + "jar", + "--from", + "eclipse-temurin:8-jdk-focal", + "--target", + "docker://spring-boot-jar-layered", + jarPath.toString()); assertThat(exitCode).isEqualTo(0); String output = - new Command("docker", "run", "--rm", "--detach", "-p8080:8080", "spring-boot-jar-layered") + new Command( + "docker", + "run", + "--rm", + "--detach", + "-p8080:8080", + "spring-boot-jar-layered", + "--privileged", + "--network=host") .run(); containerName = output.trim(); try (JarFile jarFile = new JarFile(jarPath.toFile())) { assertThat(jarFile.getEntry("BOOT-INF/layers.idx")).isNotNull(); - assertThat(getContent(new URL("http://localhost:8080"))).isEqualTo("Hello world"); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080")); } } @@ -189,16 +258,33 @@ public void testSpringBootNonLayeredJar_explodedMode() throws IOException, Inter Integer exitCode = new CommandLine(new JibCli()) - .execute("jar", "--target", "docker://spring-boot-jar", jarPath.toString()); + .execute( + "jar", + "--from", + "eclipse-temurin:8-jdk-focal", + "--target", + "docker://spring-boot-jar", + jarPath.toString()); assertThat(exitCode).isEqualTo(0); String output = - new Command("docker", "run", "--rm", "--detach", "-p8080:8080", "spring-boot-jar").run(); + new Command( + "docker", + "run", + "--rm", + "--detach", + "-p8080:8080", + "spring-boot-jar", + "--privileged", + "--network=host") + .run(); containerName = output.trim(); try (JarFile jarFile = new JarFile(jarPath.toFile())) { assertThat(jarFile.getEntry("BOOT-INF/layers.idx")).isNull(); - assertThat(getContent(new URL("http://localhost:8080"))).isEqualTo("Hello world"); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080")); } } @@ -211,6 +297,8 @@ public void testSpringBootJar_packagedMode() throws IOException, InterruptedExce new CommandLine(new JibCli()) .execute( "jar", + "--from", + "eclipse-temurin:8-jdk-focal", "--target", "docker://packaged-spring-boot", jarPath.toString(), @@ -218,44 +306,63 @@ public void testSpringBootJar_packagedMode() throws IOException, InterruptedExce assertThat(exitCode).isEqualTo(0); String output = - new Command("docker", "run", "--rm", "--detach", "-p8080:8080", "packaged-spring-boot") + new Command( + "docker", + "run", + "--rm", + "--detach", + "-p8080:8080", + "packaged-spring-boot", + "--privileged", + "--network=host") .run(); containerName = output.trim(); - assertThat(getContent(new URL("http://localhost:8080"))).isEqualTo("Hello world"); - } - - @Test - public void testJar_baseImageSpecified() - throws IOException, URISyntaxException, InterruptedException { - Path jarPath = Paths.get(Resources.getResource("jarTest/standard/noDependencyJar.jar").toURI()); - Integer exitCode = - new CommandLine(new JibCli()) - .execute( - "jar", - "--target=docker://cli-gcr-base", - "--from=gcr.io/google-appengine/openjdk:8", - jarPath.toString()); - assertThat(exitCode).isEqualTo(0); - String output = new Command("docker", "run", "--rm", "cli-gcr-base").run(); - assertThat(output).isEqualTo("Hello World"); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080")); } - @Nullable - private static String getContent(URL url) throws InterruptedException { - for (int i = 0; i < 40; i++) { - Thread.sleep(500); - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { - try (InputStream in = connection.getInputStream()) { - return Blobs.writeToString(Blobs.from(in)); - } - } - } catch (IOException ignored) { - // ignored - } + public static void createJarFile( + String name, String className, String classPath, String mainClass) + throws IOException, URISyntaxException { + Path javaFilePath = + Paths.get(Resources.getResource("jarTest/standard/" + className + ".java").toURI()); + Path workingDir = Paths.get(Resources.getResource("jarTest/standard/").toURI()); + + // compile the java file + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + Preconditions.checkNotNull(compiler); + StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); + Iterable compilationUnits = + fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(javaFilePath.toFile())); + Iterable options = Arrays.asList("-source", "1.8", "-target", "1.8"); + JavaCompiler.CompilationTask task = + compiler.getTask(null, fileManager, null, options, null, compilationUnits); + boolean success = task.call(); + assertThat(success).isTrue(); + + // Create a manifest file + Manifest manifest = new Manifest(); + Attributes attributes = new Attributes(); + attributes.putValue("Manifest-Version", "1.0"); + if (classPath != null) { + attributes.putValue("Class-Path", classPath); + } + if (mainClass != null) { + attributes.putValue("Main-Class", mainClass); + } + manifest.getMainAttributes().putAll(attributes); + + // Create JAR + File jarFile = workingDir.resolve(name).toFile(); + jarFile.getParentFile().mkdirs(); + try (FileOutputStream fileOutputStream = new FileOutputStream(jarFile); + JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream, manifest)) { + ZipEntry zipEntry = new ZipEntry(className + ".class"); + jarOutputStream.putNextEntry(zipEntry); + jarOutputStream.write(Files.readAllBytes(workingDir.resolve(className + ".class"))); + jarOutputStream.closeEntry(); } - return null; } } diff --git a/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/WarCommandTest.java b/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/WarCommandTest.java index c6739b48c4..f5ab1d1c05 100644 --- a/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/WarCommandTest.java +++ b/jib-cli/src/integration-test/java/com/google/cloud/tools/jib/cli/WarCommandTest.java @@ -19,12 +19,10 @@ import static com.google.common.truth.Truth.assertThat; import com.google.cloud.tools.jib.Command; -import com.google.cloud.tools.jib.blob.Blobs; +import com.google.cloud.tools.jib.api.HttpRequestTester; import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.net.HttpURLConnection; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; @@ -86,10 +84,21 @@ public void testWar_jetty() throws IOException, InterruptedException { .execute("war", "--target", "docker://exploded-war", warPath.toString()); assertThat(exitCode).isEqualTo(0); String output = - new Command("docker", "run", "--rm", "--detach", "-p8080:8080", "exploded-war").run(); + new Command( + "docker", + "run", + "--rm", + "--detach", + "-p8080:8080", + "exploded-war", + "--privileged", + "--network=host") + .run(); containerName = output.trim(); - assertThat(getContent(new URL("http://localhost:8080/hello"))).isEqualTo("Hello world"); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080/hello")); } @Test @@ -103,7 +112,7 @@ public void testWar_customJettySpecified() throws IOException, InterruptedExcept "war", "--target", "docker://exploded-war-custom-jetty", - "--from=jetty:9.4-jre11", + "--from=jetty:11.0-jre11-slim-openjdk", warPath.toString()); assertThat(exitCode).isEqualTo(0); String output = @@ -111,7 +120,9 @@ public void testWar_customJettySpecified() throws IOException, InterruptedExcept .run(); containerName = output.trim(); - assertThat(getContent(new URL("http://localhost:8080/hello"))).isEqualTo("Hello world"); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080/hello")); } @Test @@ -125,7 +136,7 @@ public void testWar_tomcat() throws IOException, InterruptedException { "war", "--target", "docker://exploded-war-tomcat", - "--from=tomcat:8.5-jre8-alpine", + "--from=tomcat:10-jre8-openjdk-slim", "--app-root", "/usr/local/tomcat/webapps/ROOT", warPath.toString()); @@ -135,24 +146,8 @@ public void testWar_tomcat() throws IOException, InterruptedException { .run(); containerName = output.trim(); - assertThat(getContent(new URL("http://localhost:8080/hello"))).isEqualTo("Hello world"); - } - - @Nullable - private static String getContent(URL url) throws InterruptedException { - for (int i = 0; i < 40; i++) { - Thread.sleep(500); - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { - try (InputStream in = connection.getInputStream()) { - return Blobs.writeToString(Blobs.from(in)); - } - } - } catch (IOException ignored) { - // ignored - } - } - return null; + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080/hello")); } } diff --git a/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings-layered.gradle b/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings-layered.gradle index 7c83d6b9c2..8d433d17d0 100644 --- a/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings-layered.gradle +++ b/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings-layered.gradle @@ -1,2 +1,8 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} rootProject.name = 'spring-boot' rootProject.buildFileName = 'build-layered.gradle' diff --git a/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings.gradle b/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings.gradle index ca13736740..17fb2a2bfa 100644 --- a/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings.gradle +++ b/jib-cli/src/integration-test/resources/jarTest/spring-boot/settings.gradle @@ -1 +1,7 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} rootProject.name = 'spring-boot' \ No newline at end of file diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/HelloWorld.java b/jib-cli/src/integration-test/resources/jarTest/standard/HelloWorld.java new file mode 100644 index 0000000000..558d6f9908 --- /dev/null +++ b/jib-cli/src/integration-test/resources/jarTest/standard/HelloWorld.java @@ -0,0 +1,5 @@ +public class HelloWorld { + public static void main(String[] args) { + System.out.print("Hello World"); + } +} diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/dep/A.java b/jib-cli/src/integration-test/resources/jarTest/standard/dep/A.java new file mode 100644 index 0000000000..96d933ef79 --- /dev/null +++ b/jib-cli/src/integration-test/resources/jarTest/standard/dep/A.java @@ -0,0 +1,7 @@ +package dep; + +public class A { + public static void getResult() { + System.out.print("Hello "); + } +} diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/dep2/B.java b/jib-cli/src/integration-test/resources/jarTest/standard/dep2/B.java new file mode 100644 index 0000000000..41ff5a9aa4 --- /dev/null +++ b/jib-cli/src/integration-test/resources/jarTest/standard/dep2/B.java @@ -0,0 +1,7 @@ +package dep2; + +public class B { + public static void getResult() { + System.out.print("World"); + } +} diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/dependency1.jar b/jib-cli/src/integration-test/resources/jarTest/standard/dependency1.jar deleted file mode 100644 index 84b923b25b..0000000000 Binary files a/jib-cli/src/integration-test/resources/jarTest/standard/dependency1.jar and /dev/null differ diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/directory/dependency2.jar b/jib-cli/src/integration-test/resources/jarTest/standard/directory/dependency2.jar deleted file mode 100644 index 79583ea652..0000000000 Binary files a/jib-cli/src/integration-test/resources/jarTest/standard/directory/dependency2.jar and /dev/null differ diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/jarWithCp.jar b/jib-cli/src/integration-test/resources/jarTest/standard/jarWithCp.jar deleted file mode 100644 index 0ae4bd379a..0000000000 Binary files a/jib-cli/src/integration-test/resources/jarTest/standard/jarWithCp.jar and /dev/null differ diff --git a/jib-cli/src/integration-test/resources/jarTest/standard/noDependencyJar.jar b/jib-cli/src/integration-test/resources/jarTest/standard/noDependencyJar.jar deleted file mode 100644 index 754cd1ba2e..0000000000 Binary files a/jib-cli/src/integration-test/resources/jarTest/standard/noDependencyJar.jar and /dev/null differ diff --git a/jib-cli/src/integration-test/resources/warTest/build.gradle b/jib-cli/src/integration-test/resources/warTest/build.gradle index 8124305d85..627efcfa29 100644 --- a/jib-cli/src/integration-test/resources/warTest/build.gradle +++ b/jib-cli/src/integration-test/resources/warTest/build.gradle @@ -15,8 +15,8 @@ configurations { } dependencies { - providedCompile 'javax.servlet:servlet-api:2.5' - moreLibs 'javax.annotation:javax.annotation-api:1.2' // random extra JAR + providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0' + moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR } war { diff --git a/jib-cli/src/integration-test/resources/warTest/src/main/java/example/HelloWorld.java b/jib-cli/src/integration-test/resources/warTest/src/main/java/example/HelloWorld.java index 05618c6777..ae65db32f0 100644 --- a/jib-cli/src/integration-test/resources/warTest/src/main/java/example/HelloWorld.java +++ b/jib-cli/src/integration-test/resources/warTest/src/main/java/example/HelloWorld.java @@ -16,6 +16,9 @@ package example; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -23,9 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class HelloWorld extends HttpServlet { diff --git a/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactProcessors.java b/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactProcessors.java index 073faa6281..58f9737b76 100644 --- a/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactProcessors.java +++ b/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/ArtifactProcessors.java @@ -64,13 +64,12 @@ public static ArtifactProcessor fromJar( CommonContainerConfigCliOptions commonContainerConfigCliOptions) throws IOException { Integer jarJavaVersion = determineJavaMajorVersion(jarPath); - if (jarJavaVersion > 11 && !commonContainerConfigCliOptions.getFrom().isPresent()) { + if (jarJavaVersion > 17 && !commonContainerConfigCliOptions.getFrom().isPresent()) { throw new IllegalStateException( - "The input JAR (" - + jarPath - + ") is compiled with Java " - + jarJavaVersion - + ", but the default base image only supports versions up to Java 11. Specify a custom base image with --from."); + String.format( + "The input JAR (%s) is compiled with Java %d, but the default base image only " + + "supports versions up to Java 17. Specify a custom base image with --from.", + jarPath, jarJavaVersion)); } String jarType = determineJarType(jarPath); ProcessingMode mode = jarOptions.getMode(); diff --git a/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarFiles.java b/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarFiles.java index 0de58c2f8a..ca06fdc273 100644 --- a/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarFiles.java +++ b/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/jar/JarFiles.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.util.Collections; import java.util.List; -import java.util.Optional; /** Class to build a container representation from the contents of a jar file. */ public class JarFiles { @@ -56,22 +55,10 @@ public static JibContainerBuilder toJibContainerBuilder( CommonContainerConfigCliOptions commonContainerConfigCliOptions, ConsoleLogger logger) throws IOException, InvalidImageReferenceException { - - // Use AdoptOpenJDK image as the default base image. - JibContainerBuilder containerBuilder; - Optional imageReference = commonContainerConfigCliOptions.getFrom(); - if (imageReference.isPresent()) { - containerBuilder = - ContainerBuilders.create( - imageReference.get(), Collections.emptySet(), commonCliOptions, logger); - } else { - containerBuilder = - (processor.getJavaVersion() <= 8) - ? ContainerBuilders.create( - "adoptopenjdk:8-jre", Collections.emptySet(), commonCliOptions, logger) - : ContainerBuilders.create( - "adoptopenjdk:11-jre", Collections.emptySet(), commonCliOptions, logger); - } + String imageReference = + commonContainerConfigCliOptions.getFrom().orElseGet(() -> getDefaultBaseImage(processor)); + JibContainerBuilder containerBuilder = + ContainerBuilders.create(imageReference, Collections.emptySet(), commonCliOptions, logger); List layers = processor.createLayers(); List customEntrypoint = commonContainerConfigCliOptions.getEntrypoint(); @@ -94,4 +81,17 @@ public static JibContainerBuilder toJibContainerBuilder( return containerBuilder; } + + private static String getDefaultBaseImage(ArtifactProcessor processor) { + if (processor.getJavaVersion() <= 8) { + return "eclipse-temurin:8-jre"; + } + if (processor.getJavaVersion() <= 11) { + return "eclipse-temurin:11-jre"; + } + if (processor.getJavaVersion() <= 17) { + return "eclipse-temurin:17-jre"; + } + return "eclipse-temurin:21-jre"; + } } diff --git a/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/war/WarFiles.java b/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/war/WarFiles.java index a69d26a693..ee7830dfd2 100644 --- a/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/war/WarFiles.java +++ b/jib-cli/src/main/java/com/google/cloud/tools/jib/cli/war/WarFiles.java @@ -81,7 +81,10 @@ private static List computeEntrypoint( return entrypoint; } if (commonContainerConfigCliOptions.isJettyBaseimage()) { - return ImmutableList.of("java", "-jar", "/usr/local/jetty/start.jar"); + // Since we are using Jetty 12 or later as the default, the deploy module needs to be + // specified. See + // https://eclipse.dev/jetty/documentation/jetty-12/operations-guide/index.html + return ImmutableList.of("java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy"); } return null; } diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/ArtifactProcessorsTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/ArtifactProcessorsTest.java index 61257160c3..edf8293f7c 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/ArtifactProcessorsTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/ArtifactProcessorsTest.java @@ -58,7 +58,7 @@ public class ArtifactProcessorsTest { private static final String STANDARD_WITH_INVALID_CLASS = "jar/standard/jarWithInvalidClass.jar"; private static final String STANDARD_WITH_EMPTY_CLASS_FILE = "jar/standard/standardJarWithOnlyClasses.jar"; - private static final String JAVA_14_JAR = "jar/java14WithModuleInfo.jar"; + private static final String JAVA_18_JAR = "jar/java18.jar"; @Mock private CacheDirectories mockCacheDirectories; @Mock private Jar mockJarCommand; @@ -125,7 +125,7 @@ public void testFromJar_springBootExploded() throws IOException, URISyntaxExcept @Test public void testFromJar_incompatibleDefaultBaseImage() throws URISyntaxException { - Path jarPath = Paths.get(Resources.getResource(JAVA_14_JAR).toURI()); + Path jarPath = Paths.get(Resources.getResource(JAVA_18_JAR).toURI()); IllegalStateException exception = assertThrows( @@ -139,13 +139,13 @@ public void testFromJar_incompatibleDefaultBaseImage() throws URISyntaxException assertThat(exception) .hasMessageThat() - .startsWith("The input JAR (" + jarPath + ") is compiled with Java 14"); + .startsWith("The input JAR (" + jarPath + ") is compiled with Java 18"); } @Test public void testFromJar_incompatibleDefaultBaseImage_baseImageSpecified() throws URISyntaxException, IOException { - Path jarPath = Paths.get(Resources.getResource(JAVA_14_JAR).toURI()); + Path jarPath = Paths.get(Resources.getResource(JAVA_18_JAR).toURI()); when(mockJarCommand.getMode()).thenReturn(ProcessingMode.exploded); when(mockCommonContainerConfigCliOptions.getFrom()).thenReturn(Optional.of("base-image")); diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/HttpGetVerifier.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java similarity index 67% rename from jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/HttpGetVerifier.java rename to jib-cli/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java index d19a390372..8a8856a21a 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/HttpGetVerifier.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java @@ -14,7 +14,7 @@ * the License. */ -package com.google.cloud.tools.jib.maven; +package com.google.cloud.tools.jib.api; import com.google.cloud.tools.jib.blob.Blobs; import java.io.IOException; @@ -24,17 +24,30 @@ import javax.annotation.Nullable; import org.junit.Assert; -/** Verifies the response of HTTP GET. */ -class HttpGetVerifier { +/** Test helpers for making HTTP requests. */ +public class HttpRequestTester { /** * Verifies the response body. Repeatedly tries {@code url} at the interval of .5 seconds for up * to 20 seconds until getting OK HTTP response code. */ - static void verifyBody(String expectedBody, URL url) throws InterruptedException { + public static void verifyBody(String expectedBody, URL url) throws InterruptedException { Assert.assertEquals(expectedBody, getContent(url)); } + /** Fetches the host to use for the http request. */ + public static String fetchDockerHostForHttpRequest() { + if (System.getenv("KOKORO_JOB_CLUSTER") != null + && System.getenv("KOKORO_JOB_CLUSTER").equals("MACOS_EXTERNAL")) { + return System.getenv("DOCKER_IP"); + } else if (System.getenv("KOKORO_JOB_CLUSTER") != null + && System.getenv("KOKORO_JOB_CLUSTER").equals("GCP_UBUNTU_DOCKER")) { + return System.getenv("DOCKER_IP_UBUNTU"); + } else { + return "localhost"; + } + } + @Nullable private static String getContent(URL url) throws InterruptedException { for (int i = 0; i < 40; i++) { @@ -52,4 +65,6 @@ private static String getContent(URL url) throws InterruptedException { } return null; } + + private HttpRequestTester() {} } diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/BuildTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/BuildTest.java index cb6b78305a..5eba093f58 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/BuildTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/BuildTest.java @@ -379,19 +379,19 @@ public void testParse_passwordWithoutUsername(String usernameField, String passw .isEqualTo("Error: Missing required argument(s): " + usernameField + "="); } - public Object incompatibleCredentialOptions() { - return new Object[] { - new String[] {"--credential-helper=x", "--to-credential-helper=x"}, - new String[] {"--credential-helper=x", "--from-credential-helper=x"}, - new String[] {"--credential-helper=x", "--username=x", "--password=x"}, - new String[] {"--credential-helper=x", "--from-username=x", "--from-password=x"}, - new String[] {"--credential-helper=x", "--to-username=x", "--to-password=x"}, - new String[] {"--username=x", "--password=x", "--from-username=x", "--from-password=x"}, - new String[] {"--username=x", "--password=x", "--to-username=x", "--to-password=x"}, - new String[] {"--username=x", "--password=x", "--to-credential-helper=x"}, - new String[] {"--username=x", "--password=x", "--from-credential-helper=x"}, - new String[] {"--from-credential-helper=x", "--from-username=x", "--from-password=x"}, - new String[] {"--to-credential-helper=x", "--to-password=x", "--to-username=x"}, + public String[][] incompatibleCredentialOptions() { + return new String[][] { + {"--credential-helper=x", "--to-credential-helper=x"}, + {"--credential-helper=x", "--from-credential-helper=x"}, + {"--credential-helper=x", "--username=x", "--password=x"}, + {"--credential-helper=x", "--from-username=x", "--from-password=x"}, + {"--credential-helper=x", "--to-username=x", "--to-password=x"}, + {"--username=x", "--password=x", "--from-username=x", "--from-password=x"}, + {"--username=x", "--password=x", "--to-username=x", "--to-password=x"}, + {"--username=x", "--password=x", "--to-credential-helper=x"}, + {"--username=x", "--password=x", "--from-credential-helper=x"}, + {"--from-credential-helper=x", "--from-username=x", "--from-password=x"}, + {"--to-credential-helper=x", "--to-password=x", "--to-username=x"}, }; } @@ -406,7 +406,7 @@ public void testParse_incompatibleCredentialOptions(String[] authArgs) { new Build(), ArrayUtils.add(authArgs, "--target=ignored"))); assertThat(meae) .hasMessageThat() - .containsMatch("^Error: (--(from-|to-)?credential-helper|\\[--username)"); + .containsMatch("^Error: (\\[)*(--(from-|to-)?credential-helper|\\[--(username|password))"); } @Test diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/CredentialsTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/CredentialsTest.java index 5e4a96c376..7ba6130aee 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/CredentialsTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/CredentialsTest.java @@ -17,6 +17,8 @@ package com.google.cloud.tools.jib.cli; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.plugins.common.DefaultCredentialRetrievers; @@ -30,7 +32,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import picocli.CommandLine; @@ -42,10 +43,9 @@ public class CredentialsTest { @Rule public final MockitoRule mockitoJUnit = MockitoJUnit.rule(); @Mock private DefaultCredentialRetrievers defaultCredentialRetrievers; - private Object paramsToNone() { - return new Object[] { - new String[] {"--from-credential-helper=ignored"}, - new String[] {"--from-username=ignored", "--from-password=ignored"}, + private String[][] paramsToNone() { + return new String[][] { + {"--from-credential-helper=ignored"}, {"--from-username=ignored", "--from-password=ignored"}, }; } @@ -55,14 +55,13 @@ public void testGetToCredentialRetriever_none(String[] args) throws FileNotFound CommonCliOptions commonCliOptions = CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args)); Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers); - Mockito.verify(defaultCredentialRetrievers).asList(); - Mockito.verifyNoMoreInteractions(defaultCredentialRetrievers); + verify(defaultCredentialRetrievers).asList(); + verifyNoMoreInteractions(defaultCredentialRetrievers); } - private Object paramsFromNone() { - return new Object[] { - new String[] {"--to-credential-helper=ignored"}, - new String[] {"--to-username=ignored", "--to-password=ignored"}, + private String[][] paramsFromNone() { + return new String[][] { + {"--to-credential-helper=ignored"}, {"--to-username=ignored", "--to-password=ignored"}, }; } @@ -72,18 +71,16 @@ public void testGetFromCredentialRetriever_none(String[] args) throws FileNotFou CommonCliOptions commonCliOptions = CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args)); Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers); - Mockito.verify(defaultCredentialRetrievers).asList(); - Mockito.verifyNoMoreInteractions(defaultCredentialRetrievers); + verify(defaultCredentialRetrievers).asList(); + verifyNoMoreInteractions(defaultCredentialRetrievers); } - private Object paramsToCredHelper() { - return new Object[] { - new String[] {"--credential-helper=abc"}, - new String[] {"--to-credential-helper=abc"}, - new String[] {"--to-credential-helper=abc", "--from-credential-helper=ignored"}, - new String[] { - "--to-credential-helper=abc", "--from-username=ignored", "--from-password=ignored" - }, + private String[][] paramsToCredHelper() { + return new String[][] { + {"--credential-helper=abc"}, + {"--to-credential-helper=abc"}, + {"--to-credential-helper=abc", "--from-credential-helper=ignored"}, + {"--to-credential-helper=abc", "--from-username=ignored", "--from-password=ignored"}, }; } @@ -93,19 +90,17 @@ public void testGetToCredentialRetriever_credHelper(String[] args) throws FileNo CommonCliOptions commonCliOptions = CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args)); Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers); - Mockito.verify(defaultCredentialRetrievers).setCredentialHelper("abc"); - Mockito.verify(defaultCredentialRetrievers).asList(); - Mockito.verifyNoMoreInteractions(defaultCredentialRetrievers); + verify(defaultCredentialRetrievers).setCredentialHelper("abc"); + verify(defaultCredentialRetrievers).asList(); + verifyNoMoreInteractions(defaultCredentialRetrievers); } - private Object paramsFromCredHelper() { - return new Object[] { - new String[] {"--credential-helper=abc"}, - new String[] {"--from-credential-helper=abc"}, - new String[] {"--from-credential-helper=abc", "--to-credential-helper=ignored"}, - new String[] { - "--from-credential-helper=abc", "--to-username=ignored", "--to-password=ignored" - }, + private String[][] paramsFromCredHelper() { + return new String[][] { + {"--credential-helper=abc"}, + {"--from-credential-helper=abc"}, + {"--from-credential-helper=abc", "--to-credential-helper=ignored"}, + {"--from-credential-helper=abc", "--to-username=ignored", "--to-password=ignored"}, }; } @@ -115,9 +110,9 @@ public void testGetFromCredentialHelper(String[] args) throws FileNotFoundExcept CommonCliOptions commonCliOptions = CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args)); Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers); - Mockito.verify(defaultCredentialRetrievers).setCredentialHelper("abc"); - Mockito.verify(defaultCredentialRetrievers).asList(); - Mockito.verifyNoMoreInteractions(defaultCredentialRetrievers); + verify(defaultCredentialRetrievers).setCredentialHelper("abc"); + verify(defaultCredentialRetrievers).asList(); + verifyNoMoreInteractions(defaultCredentialRetrievers); } public Object paramsToUsernamePassword() { @@ -148,11 +143,11 @@ public void testGetToUsernamePassword(String expectedSource, String[] args) CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args)); Credentials.getToCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers); ArgumentCaptor captor = ArgumentCaptor.forClass(Credential.class); - Mockito.verify(defaultCredentialRetrievers) + verify(defaultCredentialRetrievers) .setKnownCredential(captor.capture(), ArgumentMatchers.eq(expectedSource)); assertThat(captor.getValue()).isEqualTo(Credential.from("abc", "xyz")); - Mockito.verify(defaultCredentialRetrievers).asList(); - Mockito.verifyNoMoreInteractions(defaultCredentialRetrievers); + verify(defaultCredentialRetrievers).asList(); + verifyNoMoreInteractions(defaultCredentialRetrievers); } public Object paramsFromUsernamePassword() { @@ -188,10 +183,10 @@ public void testGetFromUsernamePassword(String expectedSource, String[] args) CommandLine.populateCommand(new CommonCliOptions(), ArrayUtils.addAll(DEFAULT_ARGS, args)); Credentials.getFromCredentialRetrievers(commonCliOptions, defaultCredentialRetrievers); ArgumentCaptor captor = ArgumentCaptor.forClass(Credential.class); - Mockito.verify(defaultCredentialRetrievers) + verify(defaultCredentialRetrievers) .setKnownCredential(captor.capture(), ArgumentMatchers.eq(expectedSource)); assertThat(captor.getValue()).isEqualTo(Credential.from("abc", "xyz")); - Mockito.verify(defaultCredentialRetrievers).asList(); - Mockito.verifyNoMoreInteractions(defaultCredentialRetrievers); + verify(defaultCredentialRetrievers).asList(); + verifyNoMoreInteractions(defaultCredentialRetrievers); } } diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JarTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JarTest.java index 2914aea876..528bdd6f9a 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JarTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JarTest.java @@ -407,19 +407,19 @@ public void testParse_passwordWithoutUsername(String usernameField, String passw .isEqualTo("Error: Missing required argument(s): " + usernameField + "="); } - public Object incompatibleCredentialOptions() { - return new Object[] { - new String[] {"--credential-helper=x", "--to-credential-helper=x"}, - new String[] {"--credential-helper=x", "--from-credential-helper=x"}, - new String[] {"--credential-helper=x", "--username=x", "--password=x"}, - new String[] {"--credential-helper=x", "--from-username=x", "--from-password=x"}, - new String[] {"--credential-helper=x", "--to-username=x", "--to-password=x"}, - new String[] {"--username=x", "--password=x", "--from-username=x", "--from-password=x"}, - new String[] {"--username=x", "--password=x", "--to-username=x", "--to-password=x"}, - new String[] {"--username=x", "--password=x", "--to-credential-helper=x"}, - new String[] {"--username=x", "--password=x", "--from-credential-helper=x"}, - new String[] {"--from-credential-helper=x", "--from-username=x", "--from-password=x"}, - new String[] {"--to-credential-helper=x", "--to-password=x", "--to-username=x"}, + public String[][] incompatibleCredentialOptions() { + return new String[][] { + {"--credential-helper=x", "--to-credential-helper=x"}, + {"--credential-helper=x", "--from-credential-helper=x"}, + {"--credential-helper=x", "--username=x", "--password=x"}, + {"--credential-helper=x", "--from-username=x", "--from-password=x"}, + {"--credential-helper=x", "--to-username=x", "--to-password=x"}, + {"--username=x", "--password=x", "--from-username=x", "--from-password=x"}, + {"--username=x", "--password=x", "--to-username=x", "--to-password=x"}, + {"--username=x", "--password=x", "--to-credential-helper=x"}, + {"--username=x", "--password=x", "--from-credential-helper=x"}, + {"--from-credential-helper=x", "--from-username=x", "--from-password=x"}, + {"--to-credential-helper=x", "--to-password=x", "--to-username=x"}, }; } @@ -434,7 +434,7 @@ public void testParse_incompatibleCredentialOptions(String[] authArgs) { new Jar(), ArrayUtils.addAll(authArgs, "--target=ignored", "my-app.jar"))); assertThat(meae) .hasMessageThat() - .containsMatch("^Error: (--(from-|to-)?credential-helper|\\[--username)"); + .containsMatch("^Error: (\\[)*(--(from-|to-)?credential-helper|\\[--(username|password))"); } @Test diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JibCliTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JibCliTest.java index aa51776bea..72c78a0614 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JibCliTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/JibCliTest.java @@ -127,7 +127,8 @@ public void testWriteImageJson() throws InvalidImageReferenceException, IOException, DigestException { String imageId = "sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9"; String digest = "sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc"; - when(mockJibContainer.getTargetImage()).thenReturn(ImageReference.parse("adoptopenjdk:8-jre")); + when(mockJibContainer.getTargetImage()) + .thenReturn(ImageReference.parse("eclipse-temurin:8-jre")); when(mockJibContainer.getImageId()).thenReturn(DescriptorDigest.fromDigest(imageId)); when(mockJibContainer.getDigest()).thenReturn(DescriptorDigest.fromDigest(digest)); when(mockJibContainer.getTags()).thenReturn(ImmutableSet.of("latest", "tag-2")); @@ -138,7 +139,7 @@ public void testWriteImageJson() String outputJson = new String(Files.readAllBytes(outputPath), StandardCharsets.UTF_8); ImageMetadataOutput metadataOutput = JsonTemplateMapper.readJson(outputJson, ImageMetadataOutput.class); - assertThat(metadataOutput.getImage()).isEqualTo("adoptopenjdk:8-jre"); + assertThat(metadataOutput.getImage()).isEqualTo("eclipse-temurin:8-jre"); assertThat(metadataOutput.getImageId()).isEqualTo(imageId); assertThat(metadataOutput.getImageDigest()).isEqualTo(digest); assertThat(metadataOutput.getTags()).containsExactly("latest", "tag-2"); diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/WarTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/WarTest.java index c47c1f5662..9bdc950c02 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/WarTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/WarTest.java @@ -405,19 +405,19 @@ public void testParse_passwordWithoutUsername(String usernameField, String passw .isEqualTo("Error: Missing required argument(s): " + usernameField + "="); } - public Object incompatibleCredentialOptions() { - return new Object[] { - new String[] {"--credential-helper=x", "--to-credential-helper=x"}, - new String[] {"--credential-helper=x", "--from-credential-helper=x"}, - new String[] {"--credential-helper=x", "--username=x", "--password=x"}, - new String[] {"--credential-helper=x", "--from-username=x", "--from-password=x"}, - new String[] {"--credential-helper=x", "--to-username=x", "--to-password=x"}, - new String[] {"--username=x", "--password=x", "--from-username=x", "--from-password=x"}, - new String[] {"--username=x", "--password=x", "--to-username=x", "--to-password=x"}, - new String[] {"--username=x", "--password=x", "--to-credential-helper=x"}, - new String[] {"--username=x", "--password=x", "--from-credential-helper=x"}, - new String[] {"--from-credential-helper=x", "--from-username=x", "--from-password=x"}, - new String[] {"--to-credential-helper=x", "--to-password=x", "--to-username=x"}, + public String[][] incompatibleCredentialOptions() { + return new String[][] { + {"--credential-helper=x", "--to-credential-helper=x"}, + {"--credential-helper=x", "--from-credential-helper=x"}, + {"--credential-helper=x", "--username=x", "--password=x"}, + {"--credential-helper=x", "--from-username=x", "--from-password=x"}, + {"--credential-helper=x", "--to-username=x", "--to-password=x"}, + {"--username=x", "--password=x", "--from-username=x", "--from-password=x"}, + {"--username=x", "--password=x", "--to-username=x", "--to-password=x"}, + {"--username=x", "--password=x", "--to-credential-helper=x"}, + {"--username=x", "--password=x", "--from-credential-helper=x"}, + {"--from-credential-helper=x", "--from-username=x", "--from-password=x"}, + {"--to-credential-helper=x", "--to-password=x", "--to-username=x"}, }; } @@ -432,7 +432,7 @@ public void testParse_incompatibleCredentialOptions(String[] authArgs) { new War(), ArrayUtils.addAll(authArgs, "--target=ignored", "my-app.war"))); assertThat(meae) .hasMessageThat() - .containsMatch("^Error: (--(from-|to-)?credential-helper|\\[--username)"); + .containsMatch("^Error: (\\[)*(--(from-|to-)?credential-helper|\\[--(username|password))"); } @Test diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFileSpecTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFileSpecTest.java index 0a4b9d711b..cf3f019282 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFileSpecTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFileSpecTest.java @@ -29,13 +29,13 @@ import com.google.common.collect.ImmutableList; import java.nio.file.Paths; import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; /** Tests for {@link BuildFileSpec}. */ +@RunWith(JUnitParamsRunner.class) public class BuildFileSpecTest { private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); @@ -93,7 +93,7 @@ public void testBuildFileSpec_full() throws JsonProcessingException { public void testBuildFileSpec_apiVersionRequired() { String data = "kind: BuildFile\n"; - JsonProcessingException exception = + Exception exception = assertThrows( JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); assertThat(exception) @@ -105,7 +105,7 @@ public void testBuildFileSpec_apiVersionRequired() { public void testBuildFileSpec_apiVersionNotNull() { String data = "apiVersion: null\n" + "kind: BuildFile\n"; - JsonProcessingException exception = + Exception exception = assertThrows( JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); assertThat(exception).hasMessageThat().contains("Property 'apiVersion' cannot be null"); @@ -115,7 +115,7 @@ public void testBuildFileSpec_apiVersionNotNull() { public void testBuildFileSpec_apiVersionNotEmpty() { String data = "apiVersion: ''\n" + "kind: BuildFile\n"; - JsonProcessingException exception = + Exception exception = assertThrows( JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); assertThat(exception) @@ -127,7 +127,7 @@ public void testBuildFileSpec_apiVersionNotEmpty() { public void testBuildFileSpec_kindRequired() { String data = "apiVersion: v1alpha1\n"; - JsonProcessingException exception = + Exception exception = assertThrows( JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); assertThat(exception).hasMessageThat().startsWith("Missing required creator property 'kind'"); @@ -137,7 +137,7 @@ public void testBuildFileSpec_kindRequired() { public void testBuildFileSpec_kindMustBeBuildFile() { String data = "apiVersion: v1alpha1\n" + "kind: NotBuildFile\n"; - JsonProcessingException exception = + Exception exception = assertThrows( JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); assertThat(exception) @@ -149,7 +149,7 @@ public void testBuildFileSpec_kindMustBeBuildFile() { public void testBuildFileSpec_kindNotNull() { String data = "apiVersion: v1alpha1\n" + "kind: null\n"; - JsonProcessingException exception = + Exception exception = assertThrows( JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); assertThat(exception).hasMessageThat().contains("Property 'kind' cannot be null"); @@ -169,146 +169,112 @@ public void testBuildFileSpec_nullCollections() throws JsonProcessingException { assertThat(parsed.getCmd()).isEmpty(); } - @RunWith(Parameterized.class) - public static class OptionalStringCollectionTests { - - @Parameterized.Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] {{"volumes"}, {"exposedPorts"}, {"entrypoint"}, {"cmd"}}); - } - - @Parameterized.Parameter public String fieldName; - - @Test - public void testBuildFileSpec_noNullEntries() { - String data = - "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": ['first', null]"; - - JsonProcessingException exception = - assertThrows( - JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); - assertThat(exception) - .hasMessageThat() - .contains("Property '" + fieldName + "' cannot contain null entries"); - } - - @Test - public void testBuildFileSpec_noEmptyEntries() { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": ['first', ' ']"; - - JsonProcessingException exception = - assertThrows( - JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); - assertThat(exception) - .hasMessageThat() - .contains("Property '" + fieldName + "' cannot contain empty strings"); - } - - @Test - public void testBuildFileSpec_emptyOkay() throws JsonProcessingException { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": []"; - - assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); - } - - @Test - public void testBuildFileSpec_nullOkay() throws JsonProcessingException { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": null"; - - assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); - } + @Test + @Parameters(value = {"volumes", "exposedPorts", "entrypoint", "cmd"}) + public void testBuildFileSpec_noNullEntries(String fieldName) { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": ['first', null]"; + + Exception exception = + assertThrows( + JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); + assertThat(exception) + .hasMessageThat() + .contains("Property '" + fieldName + "' cannot contain null entries"); } - @RunWith(Parameterized.class) - public static class OptionalStringTests { - - @Parameterized.Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList( - new Object[][] {{"creationTime"}, {"format"}, {"user"}, {"workingDirectory"}}); - } - - @Parameterized.Parameter public String fieldName; - - @Test - public void testBuildFileSpec_noEmptyValues() { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": ' '"; - JsonProcessingException exception = - assertThrows( - JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); - assertThat(exception) - .hasMessageThat() - .contains("Property '" + fieldName + "' cannot be an empty string"); - } - - @Test - public void testBuildFileSpec_nullOkay() throws JsonProcessingException { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": null"; - - assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); - } + @Test + @Parameters(value = {"volumes", "exposedPorts", "entrypoint", "cmd"}) + public void testBuildFileSpec_noEmptyEntries(String fieldName) { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": ['first', ' ']"; + + Exception exception = + assertThrows( + JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); + assertThat(exception) + .hasMessageThat() + .contains("Property '" + fieldName + "' cannot contain empty strings"); + } + + @Test + @Parameters(value = {"volumes", "exposedPorts", "entrypoint", "cmd"}) + public void testBuildFileSpec_emptyListOkay(String fieldName) throws JsonProcessingException { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": []"; + + assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); } - @RunWith(Parameterized.class) - public static class OptionalStringMapTests { - - @Parameterized.Parameters(name = "{0}") - public static Collection invalidMapEntries() { - return Arrays.asList( - new Object[][] { - {"environment", " key: null", "' cannot contain null values"}, - {"environment", " key: ' '", "' cannot contain empty string values"}, - {"environment", " ' ': value", "' cannot contain empty string keys"}, - {"labels", " key: null", "' cannot contain null values"}, - {"labels", " key: ' '", "' cannot contain empty string values"}, - {"labels", " ' ': value", "' cannot contain empty string keys"}, - }); - } - - @Parameterized.Parameter public String fieldName; - - @Parameterized.Parameter(1) - public String input; - - @Parameterized.Parameter(2) - public String expectedErrorMessage; - - @Test - public void testBuildFileSpec_invalidMapEntries() { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ":\n" + input; - - JsonProcessingException exception = - assertThrows( - JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); - assertThat(exception) - .hasMessageThat() - .contains("Property '" + fieldName + expectedErrorMessage); - } - - /** - * A quirk of our parser is that "null" keys are parsed as strings and not null, this test just - * formalizes that behavior. - */ - @Test - public void testBuildFileSpec_yamlNullKeysPass() throws JsonProcessingException { - String data = - "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ":\n" + " null: value"; - - assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); - } - - @Test - public void testBuildFileSpec_emptyOkay() throws JsonProcessingException { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": {}"; - - assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); - } - - @Test - public void testBuildFileSpec_nullOkay() throws JsonProcessingException { - String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": null"; - - assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); - } + @Test + @Parameters( + value = { + "volumes", + "exposedPorts", + "entrypoint", + "cmd", + "creationTime", + "format", + "user", + "workingDirectory", + "environment", + "labels" + }) + public void testBuildFileSpec_nullOkay(String fieldName) throws JsonProcessingException { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": null"; + + assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); + } + + @Test + @Parameters(value = {"creationTime", "format", "user", "workingDirectory"}) + public void testBuildFileSpec_noEmptyValues(String fieldName) { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": ' '"; + Exception exception = + assertThrows( + JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); + assertThat(exception) + .hasMessageThat() + .contains("Property '" + fieldName + "' cannot be an empty string"); + } + + @SuppressWarnings("unused") + private static String[][] invalidMapEntries() { + return new String[][] { + {"environment", " key: null", "' cannot contain null values"}, + {"environment", " key: ' '", "' cannot contain empty string values"}, + {"environment", " ' ': value", "' cannot contain empty string keys"}, + {"labels", " key: null", "' cannot contain null values"}, + {"labels", " key: ' '", "' cannot contain empty string values"}, + {"labels", " ' ': value", "' cannot contain empty string keys"}, + }; + } + + @Test + @Parameters(method = "invalidMapEntries") + public void testBuildFileSpec_invalidMapEntries( + String fieldName, String input, String errorMessage) { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ":\n" + input; + + Exception exception = + assertThrows( + JsonProcessingException.class, () -> mapper.readValue(data, BuildFileSpec.class)); + assertThat(exception).hasMessageThat().contains("Property '" + fieldName + errorMessage); + } + + // A quirk of our parser is that "null" keys are parsed as strings and not null, this test just + // formalizes that behavior. + @Test + @Parameters(value = {"environment", "labels"}) + public void testBuildFileSpec_yamlNullKeysPass(String fieldName) throws JsonProcessingException { + String data = + "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ":\n" + " null: value"; + + assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); + } + + @Test + @Parameters(value = {"environment", "labels"}) + public void testBuildFileSpec_emptyMapOkay(String fieldName) throws JsonProcessingException { + String data = "apiVersion: v1alpha1\n" + "kind: BuildFile\n" + fieldName + ": {}"; + + assertThat(mapper.readValue(data, BuildFileSpec.class)).isNotNull(); } } diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFilesTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFilesTest.java index b1e3a98d0f..3ecc77d1ec 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFilesTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/BuildFilesTest.java @@ -183,8 +183,7 @@ public void testToBuildFileSpec_templateMultiLineBehavior() Paths.get(Resources.getResource("buildfiles/projects/templating/multiLine.yaml").toURI()); Mockito.when(buildCli.getTemplateParameters()) - .thenReturn( - ImmutableMap.of("replace" + System.lineSeparator() + "this", "creationTime: 1234")); + .thenReturn(ImmutableMap.of("replace" + "\n" + "this", "creationTime: 1234")); JibContainerBuilder jibContainerBuilder = BuildFiles.toJibContainerBuilder( buildfile.getParent(), buildfile, buildCli, commonCliOptions, consoleLogger); diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/CopySpecTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/CopySpecTest.java index a20dbc6c69..4806b414af 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/CopySpecTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/CopySpecTest.java @@ -17,24 +17,22 @@ package com.google.cloud.tools.jib.cli.buildfile; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath; -import com.google.common.collect.ImmutableList; import java.nio.file.Paths; import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.Assert; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; /** Tests for {@link CopySpec}. */ +@RunWith(JUnitParamsRunner.class) public class CopySpecTest { private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); @@ -52,37 +50,24 @@ public void testCopySpec_full() throws JsonProcessingException { + " timestamp: 1\n"; CopySpec parsed = mapper.readValue(data, CopySpec.class); - Assert.assertEquals(Paths.get("target/classes"), parsed.getSrc()); - Assert.assertEquals(AbsoluteUnixPath.get("/app/classes"), parsed.getDest()); - Assert.assertEquals(ImmutableList.of("**/*.in"), parsed.getIncludes()); - Assert.assertEquals(ImmutableList.of("**/*.ex"), parsed.getExcludes()); - Assert.assertEquals(Instant.ofEpochMilli(1), parsed.getProperties().get().getTimestamp().get()); + assertThat(parsed.getSrc()).isEqualTo(Paths.get("target/classes")); + assertThat(parsed.getDest()).isEqualTo(AbsoluteUnixPath.get("/app/classes")); + assertThat(parsed.getIncludes()).containsExactly("**/*.in"); + assertThat(parsed.getExcludes()).containsExactly("**/*.ex"); + assertThat(parsed.getProperties().get().getTimestamp().get()) + .isEqualTo(Instant.ofEpochMilli(1)); } @Test - public void testCopySpec_srcRequired() { - String data = "dest: /app/classes\n"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException jpe) { - MatcherAssert.assertThat( - jpe.getMessage(), CoreMatchers.startsWith("Missing required creator property 'src'")); - } - } - - @Test - public void testCopySpec_destRequired() { - String data = "src: target/classes\n"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException jpe) { - MatcherAssert.assertThat( - jpe.getMessage(), CoreMatchers.startsWith("Missing required creator property 'dest'")); - } + @Parameters( + value = { + "dest: /app/classes\n, Missing required creator property 'src'", + "src: target/classes\n, Missing required creator property 'dest'" + }) + public void testCopySpec_required(String data, String errorMessage) { + Exception exception = + assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class)); + assertThat(exception).hasMessageThat().startsWith(errorMessage); } @Test @@ -90,8 +75,8 @@ public void testCopySpec_destEndsWithSlash() throws JsonProcessingException { String data = "src: target/classes\n" + "dest: /app/classes/"; CopySpec parsed = mapper.readValue(data, CopySpec.class); - Assert.assertEquals(AbsoluteUnixPath.get("/app/classes"), parsed.getDest()); - Assert.assertTrue(parsed.isDestEndsWithSlash()); + assertThat(parsed.getDest()).isEqualTo(AbsoluteUnixPath.get("/app/classes")); + assertThat(parsed.isDestEndsWithSlash()).isTrue(); } @Test @@ -99,124 +84,73 @@ public void testCopySpec_destDoesNotEndWithSlash() throws JsonProcessingExceptio String data = "src: target/classes\n" + "dest: /app/classes"; CopySpec parsed = mapper.readValue(data, CopySpec.class); - Assert.assertEquals(AbsoluteUnixPath.get("/app/classes"), parsed.getDest()); - Assert.assertFalse(parsed.isDestEndsWithSlash()); + assertThat(parsed.getDest()).isEqualTo(AbsoluteUnixPath.get("/app/classes")); + assertThat(parsed.isDestEndsWithSlash()).isFalse(); } @Test - public void testCopySpec_srcNotNull() { - String data = "src: null\n" + "dest: /app/classes\n"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException jpe) { - MatcherAssert.assertThat( - jpe.getMessage(), CoreMatchers.containsString("Property 'src' cannot be null")); - } + @Parameters( + value = { + "src: null\ndest: /app/classes\n, Property 'src' cannot be null", + "src: ''\ndest: /app/classes\n, Property 'src' cannot be an empty string", + "src: target/classes\ndest: null\n, Property 'dest' cannot be null", + "src: target/classes\ndest: ''\n, Property 'dest' cannot be an empty string" + }) + public void testCopySpec_nullEmptyCheck(String data, String errorMessage) { + Exception exception = + assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class)); + assertThat(exception).hasMessageThat().contains(errorMessage); } @Test - public void testCopySpec_srcNotEmpty() { - String data = "src: ''\n" + "dest: /app/classes\n"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException jpe) { - MatcherAssert.assertThat( - jpe.getMessage(), - CoreMatchers.containsString("Property 'src' cannot be an empty string")); - } + public void testCopySpec_nullCollections() throws JsonProcessingException { + String data = "src: target/classes\n" + "dest: /app/classes\n"; + + CopySpec parsed = mapper.readValue(data, CopySpec.class); + assertThat(parsed.getIncludes()).isEmpty(); + assertThat(parsed.getExcludes()).isEmpty(); } @Test - public void testCopySpec_destNotNull() { - String data = "src: target/classes\n" + "dest: null\n"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException jpe) { - MatcherAssert.assertThat( - jpe.getMessage(), CoreMatchers.containsString("Property 'dest' cannot be null")); - } + @Parameters(value = {"includes", "excludes"}) + public void testCopySpec_noNullEntries(String fieldName) { + String data = + "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": ['first', null]"; + + Exception exception = + assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class)); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Property '" + fieldName + "' cannot contain null entries"); } @Test - public void testCopySpec_destNotEmpty() { - String data = "src: target/classes\n" + "dest: ''\n"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException jpe) { - MatcherAssert.assertThat( - jpe.getMessage(), - CoreMatchers.containsString("Property 'dest' cannot be an empty string")); - } + @Parameters(value = {"includes", "excludes"}) + public void testCopySpec_noEmptyEntries(String fieldName) { + String data = "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": ['first', ' ']"; + + Exception exception = + assertThrows(JsonProcessingException.class, () -> mapper.readValue(data, CopySpec.class)); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Property '" + fieldName + "' cannot contain empty strings"); } @Test - public void testCopySpec_nullCollections() throws JsonProcessingException { - String data = "src: target/classes\n" + "dest: /app/classes\n"; + @Parameters(value = {"includes", "excludes"}) + public void testCopySpec_emptyOkay(String fieldName) throws JsonProcessingException { + String data = "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": []"; - CopySpec parsed = mapper.readValue(data, CopySpec.class); - Assert.assertEquals(ImmutableList.of(), parsed.getIncludes()); - Assert.assertEquals(ImmutableList.of(), parsed.getExcludes()); + assertThat(mapper.readValue(data, CopySpec.class)).isNotNull(); } - @RunWith(Parameterized.class) - public static class OptionalStringCollectionTests { - - @Parameterized.Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList(new Object[][] {{"includes"}, {"excludes"}}); - } - - @Parameterized.Parameter public String fieldName; - - @Test - public void testCopySpec_noNullEntries() { - String data = - "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": ['first', null]"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException ex) { - Assert.assertEquals( - "Property '" + fieldName + "' cannot contain null entries", ex.getCause().getMessage()); - } - } - - @Test - public void testCopySpec_noEmptyEntries() { - String data = - "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": ['first', ' ']"; - - try { - mapper.readValue(data, CopySpec.class); - Assert.fail(); - } catch (JsonProcessingException ex) { - Assert.assertEquals( - "Property '" + fieldName + "' cannot contain empty strings", - ex.getCause().getMessage()); - } - } - - @Test - public void testCopySpec_emptyOkay() throws JsonProcessingException { - String data = "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": []"; - - assertThat(mapper.readValue(data, CopySpec.class)).isNotNull(); - } - - @Test - public void testCopySpec_nullOkay() throws JsonProcessingException { - String data = "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": null"; - - assertThat(mapper.readValue(data, CopySpec.class)).isNotNull(); - } + @Test + @Parameters(value = {"includes", "excludes"}) + public void testCopySpec_nullOkay(String fieldName) throws JsonProcessingException { + String data = "src: target/classes\n" + "dest: /app/classes\n" + fieldName + ": null"; + + assertThat(mapper.readValue(data, CopySpec.class)).isNotNull(); } } diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesSpecTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesSpecTest.java index 5131df2952..69ec668088 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesSpecTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/buildfile/FilePropertiesSpecTest.java @@ -16,6 +16,9 @@ package com.google.cloud.tools.jib.cli.buildfile; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -23,16 +26,13 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.cloud.tools.jib.api.buildplan.FilePermissions; import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.Assert; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; /** Tests for {@link FilePropertiesSpec}. */ +@RunWith(JUnitParamsRunner.class) public class FilePropertiesSpecTest { private static final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); @@ -47,38 +47,38 @@ public void testFilePropertiesSpec_full() throws JsonProcessingException { + "timestamp: 1\n"; FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class); - Assert.assertEquals(FilePermissions.fromOctalString("644"), parsed.getFilePermissions().get()); - Assert.assertEquals( - FilePermissions.fromOctalString("755"), parsed.getDirectoryPermissions().get()); - Assert.assertEquals("goose", parsed.getUser().get()); - Assert.assertEquals("birds", parsed.getGroup().get()); - Assert.assertEquals(Instant.ofEpochMilli(1), parsed.getTimestamp().get()); + assertThat(parsed.getFilePermissions().get()).isEqualTo(FilePermissions.fromOctalString("644")); + assertThat(parsed.getDirectoryPermissions().get()) + .isEqualTo(FilePermissions.fromOctalString("755")); + assertThat(parsed.getUser().get()).isEqualTo("goose"); + assertThat(parsed.getGroup().get()).isEqualTo("birds"); + assertThat(parsed.getTimestamp().get()).isEqualTo(Instant.ofEpochMilli(1)); } @Test - public void testFilePropertiesSpec_badFilePermissions() throws JsonProcessingException { + public void testFilePropertiesSpec_badFilePermissions() { String data = "filePermissions: 888"; - try { - mapper.readValue(data, FilePropertiesSpec.class); - Assert.fail(); - } catch (JsonMappingException ex) { - Assert.assertEquals( - "octalPermissions must be a 3-digit octal number (000-777)", ex.getCause().getMessage()); - } + Exception exception = + assertThrows( + JsonMappingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class)); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("octalPermissions must be a 3-digit octal number (000-777)"); } @Test - public void testFilePropertiesSpec_badDirectoryPermissions() throws JsonProcessingException { + public void testFilePropertiesSpec_badDirectoryPermissions() { String data = "directoryPermissions: 888"; - try { - mapper.readValue(data, FilePropertiesSpec.class); - Assert.fail(); - } catch (JsonMappingException ex) { - Assert.assertEquals( - "octalPermissions must be a 3-digit octal number (000-777)", ex.getCause().getMessage()); - } + Exception exception = + assertThrows( + JsonMappingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class)); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("octalPermissions must be a 3-digit octal number (000-777)"); } @Test @@ -86,71 +86,57 @@ public void testFilePropertiesSpec_timestampSpecIso8601() throws JsonProcessingE String data = "timestamp: 2020-06-08T14:54:36+00:00"; FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class); - Assert.assertEquals(Instant.parse("2020-06-08T14:54:36Z"), parsed.getTimestamp().get()); + assertThat(parsed.getTimestamp().get()).isEqualTo(Instant.parse("2020-06-08T14:54:36Z")); } @Test - public void testFilePropertiesSpec_badTimestamp() throws JsonProcessingException { + public void testFilePropertiesSpec_badTimestamp() { String data = "timestamp: hi"; - try { - mapper.readValue(data, FilePropertiesSpec.class); - Assert.fail(); - } catch (JsonMappingException ex) { - Assert.assertEquals( - "timestamp must be a number of milliseconds since epoch or an ISO 8601 formatted date", - ex.getCause().getMessage()); - } + Exception exception = + assertThrows( + JsonMappingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class)); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo( + "timestamp must be a number of milliseconds since epoch or an ISO 8601 formatted date"); } @Test - public void testFilePropertiesSpec_failOnUnknown() throws JsonProcessingException { + public void testFilePropertiesSpec_failOnUnknown() { String data = "badkey: badvalue"; - try { - mapper.readValue(data, FilePropertiesSpec.class); - Assert.fail(); - } catch (UnrecognizedPropertyException upe) { - MatcherAssert.assertThat( - upe.getMessage(), CoreMatchers.containsString("Unrecognized field \"badkey\"")); - } + Exception exception = + assertThrows( + UnrecognizedPropertyException.class, + () -> mapper.readValue(data, FilePropertiesSpec.class)); + assertThat(exception).hasMessageThat().contains("Unrecognized field \"badkey\""); + } + + @Test + @Parameters(value = {"filePermissions", "directoryPermissions", "user", "group", "timestamp"}) + public void testFilePropertiesSpec_noEmptyValues(String fieldName) { + String data = fieldName + ": ' '"; + + Exception exception = + assertThrows( + JsonProcessingException.class, () -> mapper.readValue(data, FilePropertiesSpec.class)); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Property '" + fieldName + "' cannot be an empty string"); } - @RunWith(Parameterized.class) - public static class OptionalStringTests { - - @Parameterized.Parameters(name = "{0}") - public static Collection data() { - return Arrays.asList( - new Object[][] { - {"filePermissions"}, {"directoryPermissions"}, {"user"}, {"group"}, {"timestamp"} - }); - } - - @Parameterized.Parameter public String fieldName; - - @Test - public void testFilePropertiesSpec_noEmptyValues() { - String data = fieldName + ": ' '"; - - try { - mapper.readValue(data, FilePropertiesSpec.class); - Assert.fail(); - } catch (JsonProcessingException ex) { - Assert.assertEquals( - "Property '" + fieldName + "' cannot be an empty string", ex.getCause().getMessage()); - } - } - - @Test - public void testFilePropertiesSpec_nullOkay() throws JsonProcessingException { - String data = fieldName + ": null"; - - FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class); - Assert.assertFalse(parsed.getFilePermissions().isPresent()); - Assert.assertFalse(parsed.getDirectoryPermissions().isPresent()); - Assert.assertFalse(parsed.getUser().isPresent()); - Assert.assertFalse(parsed.getGroup().isPresent()); - } + @Test + @Parameters(value = {"filePermissions", "directoryPermissions", "user", "group", "timestamp"}) + public void testFilePropertiesSpec_nullOkay(String fieldName) throws JsonProcessingException { + String data = fieldName + ": null"; + + FilePropertiesSpec parsed = mapper.readValue(data, FilePropertiesSpec.class); + assertThat(parsed.getFilePermissions().isPresent()).isFalse(); + assertThat(parsed.getDirectoryPermissions().isPresent()).isFalse(); + assertThat(parsed.getUser().isPresent()).isFalse(); + assertThat(parsed.getGroup().isPresent()).isFalse(); } } diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/JarFilesTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/JarFilesTest.java index b1d2838759..225d242ca0 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/JarFilesTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/jar/JarFilesTest.java @@ -40,51 +40,43 @@ import java.time.Instant; import java.util.Arrays; import java.util.Optional; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** Tests for {@link JarFiles}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnitParamsRunner.class) public class JarFilesTest { - @Mock private StandardExplodedProcessor mockStandardExplodedProcessor; + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule().silent(); + @Mock private StandardExplodedProcessor mockStandardExplodedProcessor; @Mock private StandardPackagedProcessor mockStandardPackagedProcessor; - @Mock private SpringBootExplodedProcessor mockSpringBootExplodedProcessor; - @Mock private SpringBootPackagedProcessor mockSpringBootPackagedProcessor; - @Mock private Jar mockJarCommand; - @Mock private CommonCliOptions mockCommonCliOptions; - @Mock private CommonContainerConfigCliOptions mockCommonContainerConfigCliOptions; - @Mock private ConsoleLogger mockLogger; @Test - public void testToJibContainer_defaultBaseImage_java8() - throws IOException, InvalidImageReferenceException { - when(mockStandardExplodedProcessor.getJavaVersion()).thenReturn(8); - JibContainerBuilder containerBuilder = - JarFiles.toJibContainerBuilder( - mockStandardExplodedProcessor, - mockJarCommand, - mockCommonCliOptions, - mockCommonContainerConfigCliOptions, - mockLogger); - ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan(); - - assertThat(buildPlan.getBaseImage()).isEqualTo("adoptopenjdk:8-jre"); - } - - @Test - public void testToJibContainer_defaultBaseImage_java9() + @Parameters( + value = { + "8, eclipse-temurin:8-jre", + "9, eclipse-temurin:11-jre", + "11, eclipse-temurin:11-jre", + "13, eclipse-temurin:17-jre", + "17, eclipse-temurin:17-jre", + "21, eclipse-temurin:21-jre", + }) + public void testToJibContainer_defaultBaseImage(int javaVersion, String expectedBaseImage) throws IOException, InvalidImageReferenceException { - when(mockStandardExplodedProcessor.getJavaVersion()).thenReturn(9); + when(mockStandardExplodedProcessor.getJavaVersion()).thenReturn(javaVersion); JibContainerBuilder containerBuilder = JarFiles.toJibContainerBuilder( mockStandardExplodedProcessor, @@ -94,7 +86,7 @@ public void testToJibContainer_defaultBaseImage_java9() mockLogger); ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan(); - assertThat(buildPlan.getBaseImage()).isEqualTo("adoptopenjdk:11-jre"); + assertThat(buildPlan.getBaseImage()).isEqualTo(expectedBaseImage); } @Test @@ -123,7 +115,7 @@ public void testToJibContainerBuilder_explodedStandard_basicInfo() mockLogger); ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan(); - assertThat(buildPlan.getBaseImage()).isEqualTo("adoptopenjdk:8-jre"); + assertThat(buildPlan.getBaseImage()).isEqualTo("eclipse-temurin:8-jre"); assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform("amd64", "linux"))); assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH); assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker); @@ -172,7 +164,7 @@ public void testToJibContainerBuilder_packagedStandard_basicInfo() mockLogger); ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan(); - assertThat(buildPlan.getBaseImage()).isEqualTo("adoptopenjdk:8-jre"); + assertThat(buildPlan.getBaseImage()).isEqualTo("eclipse-temurin:8-jre"); assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform("amd64", "linux"))); assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH); assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker); @@ -223,7 +215,7 @@ public void testToJibContainerBuilder_explodedLayeredSpringBoot_basicInfo() mockLogger); ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan(); - assertThat(buildPlan.getBaseImage()).isEqualTo("adoptopenjdk:8-jre"); + assertThat(buildPlan.getBaseImage()).isEqualTo("eclipse-temurin:8-jre"); assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform("amd64", "linux"))); assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH); assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker); @@ -272,7 +264,7 @@ public void testToJibContainerBuilder_packagedSpringBoot_basicInfo() mockLogger); ContainerBuildPlan buildPlan = containerBuilder.toContainerBuildPlan(); - assertThat(buildPlan.getBaseImage()).isEqualTo("adoptopenjdk:8-jre"); + assertThat(buildPlan.getBaseImage()).isEqualTo("eclipse-temurin:8-jre"); assertThat(buildPlan.getPlatforms()).isEqualTo(ImmutableSet.of(new Platform("amd64", "linux"))); assertThat(buildPlan.getCreationTime()).isEqualTo(Instant.EPOCH); assertThat(buildPlan.getFormat()).isEqualTo(ImageFormat.Docker); diff --git a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/war/WarFilesTest.java b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/war/WarFilesTest.java index 513d7bc559..5ad6ae139f 100644 --- a/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/war/WarFilesTest.java +++ b/jib-cli/src/test/java/com/google/cloud/tools/jib/cli/war/WarFilesTest.java @@ -74,7 +74,7 @@ public void testToJibContainerBuilder_explodedStandard_basicInfo() assertThat(buildPlan.getBaseImage()).isEqualTo("jetty"); assertThat(buildPlan.getEntrypoint()) - .containsExactly("java", "-jar", "/usr/local/jetty/start.jar") + .containsExactly("java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy") .inOrder(); assertThat(buildPlan.getLayers()).hasSize(1); assertThat(buildPlan.getLayers().get(0).getName()).isEqualTo("classes"); diff --git a/jib-cli/src/test/resources/jar/java14WithModuleInfo.jar b/jib-cli/src/test/resources/jar/java14WithModuleInfo.jar deleted file mode 100644 index 068db95cc8..0000000000 Binary files a/jib-cli/src/test/resources/jar/java14WithModuleInfo.jar and /dev/null differ diff --git a/jib-cli/src/test/resources/jar/java18.jar b/jib-cli/src/test/resources/jar/java18.jar new file mode 100644 index 0000000000..3466c62aa4 Binary files /dev/null and b/jib-cli/src/test/resources/jar/java18.jar differ diff --git a/jib-core/CHANGELOG.md b/jib-core/CHANGELOG.md index d5fef9a264..aa2a13f7cf 100644 --- a/jib-core/CHANGELOG.md +++ b/jib-core/CHANGELOG.md @@ -9,6 +9,89 @@ All notable changes to this project will be documented in this file. ### Fixed +## 0.27.2 +- fix: allow pushing images with different arch/os to docker daemon [#4265](https://github.com/GoogleContainerTools/jib/issues/4265) +- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4265) + +## 0.27.1 + +### Fixed +- 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)) + + +## 0.27.0 + +### Changed +- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) + +### Fixed +- fix: set PAX headers to address build reproducibility issue ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) + +## 0.26.0 + +### Fixed +- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171)) + +## 0.25.0 + +### Changed +- 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/)) +- deps: bump com.fasterxml.jackson:jackson-bom from 2.15.0 to 2.15.2. ([#4055](https://github.com/GoogleContainerTools/jib/pull/4055)) +- deps: bump com.google.guava:guava from 32.0.1-jre to 32.1.2-jre ([#4078](https://github.com/GoogleContainerTools/jib/pull/4078)) +- deps: bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9. ([#4098](https://github.com/GoogleContainerTools/jib/pull/4098)) + +### Fixed +- fix: fix WWW-Authenticate header parsing for Basic authentication ([#4035](https://github.com/GoogleContainerTools/jib/pull/4035/)) + +## 0.24.0 + +### Changed +- Replaced deprecated usages of `com.google.api.client.util.Base64` with `java.util.Base64` ([#3872](https://github.com/GoogleContainerTools/jib/pull/3872)) +- Replaced deprecated usages of `ObjectMapper.configure` in jackson ([#3890](https://github.com/GoogleContainerTools/jib/pull/3890)) + +### Fixed +- Fixed `V22ManifestListTemplate` cast to allow pulling an OCI index manifest from cache ([#3974](https://github.com/GoogleContainerTools/jib/pull/3974)) +- Specified `CompressorStreamFactory` to decompress compressed layer until EOF in `CacheStorageWriter` ([#3983](https://github.com/GoogleContainerTools/jib/pull/3983)) +- Fixed multithreading issue from `DockerClientResolver.resolve` by not sharing a static `ServiceLoader` instance ([#3993](https://github.com/GoogleContainerTools/jib/pull/3993)) + +Thanks to our community contributors @Sineaggi, @rquinio, @patrickpichler, @erdi! + +## 0.23.0 + +### Changed +- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745)) +- Re-synchronized jackson dependencies with BOM to use latest versions ([#3768](https://github.com/GoogleContainerTools/jib/pull/3768)) + +### Fixed +- Fixed partially cached base image authorization issue by adding check for existence of layers in cache ([#3767](https://github.com/GoogleContainerTools/jib/pull/3767)) + +## 0.22.0 + +### Added +- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)). +- 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)). +- Support for OCI image index manifests ([#3715](https://github.com/GoogleContainerTools/jib/pull/3715)). +- Support for base image layer compressed with zstd ([#3717](https://github.com/GoogleContainerTools/jib/pull/3717)) + +### Changed +- Upgraded slf4j-api to 2.0.0 ([#3734](https://github.com/GoogleContainerTools/jib/pull/3734), [#3735](https://github.com/GoogleContainerTools/jib/pull/3735)). +- Upgraded nullaway to 0.9.9. ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720)) +- 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)). +- 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)). + +Thanks to our community contributors, @oliver-brm, @eddumelendez, @rquinio, @gsquared94! + +## 0.21.0 + +### Added +- Support for configuration of credential helper with environment variables ([#3575](https://github.com/GoogleContainerTools/jib/pull/3575)). +- Support architecture suffixes in tags when publishing multi-platform images ([#3523](https://github.com/GoogleContainerTools/jib/pull/3523)). + +### Changed +- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/pull/3612)). +- Added helpful pointers for unsupported class file version exception cause ([#3499](https://github.com/GoogleContainerTools/jib/pull/3499)). + + ## 0.20.0 ### Added @@ -21,6 +104,10 @@ All notable changes to this project will be documented in this file. ## 0.19.0 +### Added + +- 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)) + ### Changed - `JavaContainerBuilder#fromDistroless()` and `JavaContainerBuilder#fromDistrolessJetty()` are deprecated. To migrate, check the Javadoc. ([#3123](https://github.com/GoogleContainerTools/jib/pull/3123)) diff --git a/jib-core/README.md b/jib-core/README.md index 182689aa7e..223aa6a4bd 100644 --- a/jib-core/README.md +++ b/jib-core/README.md @@ -22,7 +22,7 @@ Add Jib Core as a dependency using Maven: com.google.cloud.tools jib-core - 0.20.0 + 0.27.2 ``` @@ -30,7 +30,7 @@ Add Jib Core as a dependency using Gradle: ```groovy dependencies { - compile 'com.google.cloud.tools:jib-core:0.20.0' + compile 'com.google.cloud.tools:jib-core:0.27.2' } ``` diff --git a/jib-core/build.gradle b/jib-core/build.gradle index 2ccc61525e..f31340e269 100644 --- a/jib-core/build.gradle +++ b/jib-core/build.gradle @@ -4,6 +4,14 @@ plugins { id 'eclipse' } +java { + // Feature to handle base image layers compressed with zstd instead of gzip + // Will need to re-assess the optional dependency when zstd becomes widely used + registerFeature('zstdSupport') { + usingSourceSet(sourceSets.main) + } +} + dependencies { api dependencyStrings.BUILD_PLAN implementation dependencyStrings.GOOGLE_HTTP_CLIENT @@ -11,7 +19,9 @@ dependencies { implementation dependencyStrings.GOOGLE_AUTH_LIBRARY_OAUTH2_HTTP implementation dependencyStrings.COMMONS_COMPRESS + zstdSupportImplementation dependencyStrings.ZSTD_JNI implementation dependencyStrings.GUAVA + implementation(platform(dependencyStrings.JACKSON_BOM)) implementation dependencyStrings.JACKSON_DATABIND implementation dependencyStrings.JACKSON_DATATYPE_JSR310 implementation dependencyStrings.ASM @@ -22,6 +32,7 @@ dependencies { testImplementation dependencyStrings.MOCKITO_CORE testImplementation dependencyStrings.SLF4J_API testImplementation dependencyStrings.SYSTEM_RULES + testImplementation dependencyStrings.ZSTD_JNI integrationTestImplementation dependencyStrings.JBCRYPT } diff --git a/jib-core/examples/build.gradle/README.md b/jib-core/examples/build.gradle/README.md index f9900fe561..2211f37846 100644 --- a/jib-core/examples/build.gradle/README.md +++ b/jib-core/examples/build.gradle/README.md @@ -10,10 +10,11 @@ For example, the following snippet is a simple example that creates a Gradle tas // Imports Jib Core as a library to use in this build script. buildscript { repositories { + mavenLocal() mavenCentral() } dependencies { - classpath 'com.google.cloud.tools:jib-core:0.16.0' + classpath 'com.google.cloud.tools:jib-core:0.27.0' } } @@ -41,11 +42,11 @@ task('dojib') { // Tells Jib to get registry credentials from a Docker config. .addCredentialRetriever( CredentialRetrieverFactory - .forImage(ImageReference.parse(targetImage)) + .forImage( + ImageReference.parse(targetImage), + logEvent -> logger.log(LogLevel.valueOf(logEvent.getLevel().name()), logEvent.getMessage())) .dockerConfig()))) println 'done' } } ``` - - diff --git a/jib-core/gradle.properties b/jib-core/gradle.properties index 2e9cc86f3c..1d352d7234 100644 --- a/jib-core/gradle.properties +++ b/jib-core/gradle.properties @@ -1 +1 @@ -version = 0.20.1-SNAPSHOT +version = 0.27.3-SNAPSHOT diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ContainerizerIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ContainerizerIntegrationTest.java index 48728492d6..8715ffb784 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ContainerizerIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ContainerizerIntegrationTest.java @@ -54,6 +54,8 @@ public class ContainerizerIntegrationTest { @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties(); + private final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; /** * Helper class to hold a {@link ProgressEventHandler} and verify that it handles a full progress. @@ -113,28 +115,24 @@ private static FileEntriesLayer makeLayerConfiguration( private static void assertDockerInspect(String imageReference) throws IOException, InterruptedException { - String dockerContainerConfig = new Command("docker", "inspect", imageReference).run(); + String dockerInspectExposedPorts = + new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference) + .run(); + String dockerInspectLabels = + new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run(); + String dockerConfigEnv = + new Command("docker", "inspect", "-f", "{{.Config.Env}}", imageReference).run(); + String history = new Command("docker", "history", imageReference).run(); + MatcherAssert.assertThat( - dockerContainerConfig, + dockerInspectExposedPorts, CoreMatchers.containsString( - " \"ExposedPorts\": {\n" - + " \"1000/tcp\": {},\n" - + " \"2000/tcp\": {},\n" - + " \"2001/tcp\": {},\n" - + " \"2002/tcp\": {},\n" - + " \"3000/udp\": {}")); + "\"1000/tcp\":{},\"2000/tcp\":{},\"2001/tcp\":{},\"2002/tcp\":{},\"3000/udp\":{}")); MatcherAssert.assertThat( - dockerContainerConfig, - CoreMatchers.containsString( - " \"Labels\": {\n" - + " \"key1\": \"value1\",\n" - + " \"key2\": \"value2\"\n" - + " }")); - String dockerConfigEnv = - new Command("docker", "inspect", "-f", "{{.Config.Env}}", imageReference).run(); + dockerInspectLabels, + CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\"")); MatcherAssert.assertThat(dockerConfigEnv, CoreMatchers.containsString("env1=envvalue1")); MatcherAssert.assertThat(dockerConfigEnv, CoreMatchers.containsString("env2=envvalue2")); - String history = new Command("docker", "history", imageReference).run(); MatcherAssert.assertThat(history, CoreMatchers.containsString("jib-integration-test")); MatcherAssert.assertThat(history, CoreMatchers.containsString("bazel build ...")); } @@ -156,7 +154,7 @@ public void testSteps_forBuildToDockerRegistry() throws IOException, InterruptedException, ExecutionException, RegistryException, CacheDirectoryCreationException, InvalidImageReferenceException { System.setProperty("jib.alwaysCacheBaseImage", "true"); - String imageReference = "localhost:5000/testimage:testtag"; + String imageReference = dockerHost + ":" + "5000/testimage:testtag"; Path cacheDirectory = temporaryFolder.newFolder().toPath(); Containerizer containerizer = Containerizer.to(RegistryImage.named(imageReference)) @@ -192,7 +190,7 @@ public void testSteps_forBuildToDockerRegistry() Assert.assertEquals( "Hello, world. An argument.\n", new Command("docker", "run", "--rm", imageReference).run()); - String imageReferenceByDigest = "localhost:5000/testimage@" + image1.getDigest(); + String imageReferenceByDigest = dockerHost + ":5000/testimage@" + image1.getDigest(); localRegistry.pull(imageReferenceByDigest); assertDockerInspect(imageReferenceByDigest); Assert.assertEquals( @@ -206,23 +204,23 @@ public void testSteps_forBuildToDockerRegistry_multipleTags() CacheDirectoryCreationException, InvalidImageReferenceException { buildImage( ImageReference.of("gcr.io", "distroless/java", DISTROLESS_DIGEST), - Containerizer.to(RegistryImage.named("localhost:5000/testimage:testtag")), + Containerizer.to(RegistryImage.named(dockerHost + ":5000/testimage:testtag")), Arrays.asList("testtag2", "testtag3")); - String imageReference = "localhost:5000/testimage:testtag"; + String imageReference = dockerHost + ":5000/testimage:testtag"; localRegistry.pull(imageReference); assertDockerInspect(imageReference); Assert.assertEquals( "Hello, world. An argument.\n", new Command("docker", "run", "--rm", imageReference).run()); - String imageReference2 = "localhost:5000/testimage:testtag2"; + String imageReference2 = dockerHost + ":5000/testimage:testtag2"; localRegistry.pull(imageReference2); assertDockerInspect(imageReference2); Assert.assertEquals( "Hello, world. An argument.\n", new Command("docker", "run", "--rm", imageReference2).run()); - String imageReference3 = "localhost:5000/testimage:testtag3"; + String imageReference3 = dockerHost + ":5000/testimage:testtag3"; localRegistry.pull(imageReference3); assertDockerInspect(imageReference3); Assert.assertEquals( @@ -239,24 +237,24 @@ public void testSteps_forBuildToDockerRegistry_skipExistingDigest() JibContainer image1 = buildImage( ImageReference.scratch(), - Containerizer.to(RegistryImage.named("localhost:5000/testimagerepo:testtag")), + Containerizer.to(RegistryImage.named(dockerHost + ":5000/testimagerepo:testtag")), Collections.singletonList("testtag2")); // Test that the initial image with the original tag has been pushed. - localRegistry.pull("localhost:5000/testimagerepo:testtag"); + localRegistry.pull(dockerHost + ":5000/testimagerepo:testtag"); // Test that any additional tags have also been pushed with the original image. - localRegistry.pull("localhost:5000/testimagerepo:testtag2"); + localRegistry.pull(dockerHost + ":5000/testimagerepo:testtag2"); // Push the same image with a different tag, with SKIP_EXISTING_IMAGES enabled. JibContainer image2 = buildImage( ImageReference.scratch(), - Containerizer.to(RegistryImage.named("localhost:5000/testimagerepo:new_testtag")), + Containerizer.to(RegistryImage.named(dockerHost + ":5000/testimagerepo:new_testtag")), Collections.emptyList()); // Test that the pull request throws an exception, indicating that the new tag was not pushed. try { - localRegistry.pull("localhost:5000/testimagerepo:new_testtag"); + localRegistry.pull(dockerHost + ":5000/testimagerepo:new_testtag"); Assert.fail( "jib.skipExistingImages was enabled and digest was already pushed, " + "hence new_testtag shouldn't have been pushed."); @@ -264,12 +262,16 @@ public void testSteps_forBuildToDockerRegistry_skipExistingDigest() MatcherAssert.assertThat( ex.getMessage(), CoreMatchers.containsString( - "manifest for localhost:5000/testimagerepo:new_testtag not found")); + "manifest for " + dockerHost + ":5000/testimagerepo:new_testtag not found")); } // Test that both images have the same properties. Assert.assertEquals(image1.getDigest(), image2.getDigest()); Assert.assertEquals(image1.getImageId(), image2.getImageId()); + + // Test that the first image was pushed while the second one was skipped + Assert.assertTrue(image1.isImagePushed()); + Assert.assertFalse(image2.isImagePushed()); } @Test @@ -278,10 +280,10 @@ public void testBuildToDockerRegistry_dockerHubBaseImage() RegistryException, CacheDirectoryCreationException { buildImage( ImageReference.parse("openjdk:8-jre-slim"), - Containerizer.to(RegistryImage.named("localhost:5000/testimage:testtag")), + Containerizer.to(RegistryImage.named(dockerHost + ":5000/testimage:testtag")), Collections.emptyList()); - String imageReference = "localhost:5000/testimage:testtag"; + String imageReference = dockerHost + ":5000/testimage:testtag"; new Command("docker", "pull", imageReference).run(); Assert.assertEquals( "Hello, world. An argument.\n", new Command("docker", "run", "--rm", imageReference).run()); diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java index bec137a024..bf60f5d025 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java @@ -49,18 +49,35 @@ /** Integration tests for {@link Jib}. */ public class JibIntegrationTest { + /** A known oci index sha for gcr.io/distroless/base. */ + public static final String KNOWN_OCI_INDEX_SHA = + "sha256:2c50b819aa3bfaf6ae72e47682f6c5abc0f647cf3f4224a4a9be97dd30433909"; + @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000); @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + private String imageToDelete; + + private final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; + private final RegistryClient registryClient = RegistryClient.factory( EventHandlers.NONE, - "localhost:5000", + dockerHost + ":5000", "jib-scratch", new FailoverHttpClient(true, true, ignored -> {})) .newRegistryClient(); + private final RegistryClient distrolessRegistryClient = + RegistryClient.factory( + EventHandlers.NONE, + dockerHost + ":5000", + "jib-distroless", + new FailoverHttpClient(true, true, ignored -> {})) + .newRegistryClient(); + /** * Pulls a built image and attempts to run it. * @@ -86,17 +103,20 @@ public void setUp() { } @After - public void tearDown() { + public void tearDown() throws IOException, InterruptedException { System.clearProperty("sendCredentialsOverHttp"); + if (imageToDelete != null) { + new Command("docker", "rmi", imageToDelete).run(); + } } @Test public void testBasic_helloWorld() throws InvalidImageReferenceException, InterruptedException, CacheDirectoryCreationException, IOException, RegistryException, ExecutionException { - String toImage = "localhost:5000/basic-helloworld"; + String toImage = dockerHost + ":5000/basic-helloworld"; JibContainer jibContainer = - Jib.from("localhost:5000/busybox") + Jib.from(dockerHost + ":5000/busybox") .setEntrypoint("echo", "Hello World") .containerize( Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true)); @@ -104,15 +124,16 @@ public void testBasic_helloWorld() Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage)); Assert.assertEquals( "Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest())); + imageToDelete = toImage; } @Test public void testBasic_dockerDaemonBaseImage() throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, RegistryException, CacheDirectoryCreationException { - String toImage = "localhost:5000/basic-dockerdaemon"; + String toImage = dockerHost + ":5000/basic-dockerdaemon"; JibContainer jibContainer = - Jib.from("docker://localhost:5000/busybox") + Jib.from("docker://" + dockerHost + ":5000/busybox") .setEntrypoint("echo", "Hello World") .containerize( Containerizer.to(RegistryImage.named(toImage)).setAllowInsecureRegistries(true)); @@ -120,18 +141,21 @@ public void testBasic_dockerDaemonBaseImage() Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage)); Assert.assertEquals( "Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest())); + imageToDelete = toImage; } @Test public void testBasic_dockerDaemonBaseImageToDockerDaemon() throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, RegistryException, CacheDirectoryCreationException { - Jib.from(DockerDaemonImage.named("localhost:5000/busybox")) + String toImage = dockerHost + ":5000/docker-to-docker"; + Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox")) .setEntrypoint("echo", "Hello World") - .containerize(Containerizer.to(DockerDaemonImage.named("localhost:5000/docker-to-docker"))); + .containerize(Containerizer.to(DockerDaemonImage.named(toImage))); - String output = new Command("docker", "run", "--rm", "localhost:5000/docker-to-docker").run(); + String output = new Command("docker", "run", "--rm", toImage).run(); Assert.assertEquals("Hello World\n", output); + imageToDelete = toImage; } @Test @@ -139,9 +163,9 @@ public void testBasic_tarBaseImage_dockerSavedCommand() throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, RegistryException, CacheDirectoryCreationException { Path path = temporaryFolder.getRoot().toPath().resolve("docker-save.tar"); - new Command("docker", "save", "localhost:5000/busybox", "-o=" + path).run(); + new Command("docker", "save", dockerHost + ":5000/busybox", "-o=" + path).run(); - String toImage = "localhost:5000/basic-dockersavedcommand"; + String toImage = dockerHost + ":5000/basic-dockersavedcommand"; JibContainer jibContainer = Jib.from("tar://" + path) .setEntrypoint("echo", "Hello World") @@ -151,6 +175,7 @@ public void testBasic_tarBaseImage_dockerSavedCommand() Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage)); Assert.assertEquals( "Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest())); + imageToDelete = toImage; } @Test @@ -160,7 +185,7 @@ public void testBasic_tarBaseImage_dockerSavedFile() // tar saved with 'docker save busybox -o busybox.tar' Path path = Paths.get(Resources.getResource("core/busybox-docker.tar").toURI()); - String toImage = "localhost:5000/basic-dockersavedfile"; + String toImage = dockerHost + ":5000/basic-dockersavedfile"; JibContainer jibContainer = Jib.from(TarImage.at(path).named("ignored")) .setEntrypoint("echo", "Hello World") @@ -177,14 +202,14 @@ public void testBasic_tarBaseImage_jibImage() throws InvalidImageReferenceException, InterruptedException, ExecutionException, RegistryException, CacheDirectoryCreationException, IOException, URISyntaxException { Path outputPath = temporaryFolder.getRoot().toPath().resolve("jib-image.tar"); - Jib.from("localhost:5000/busybox") + Jib.from(dockerHost + ":5000/busybox") .addLayer( Collections.singletonList(Paths.get(Resources.getResource("core/hello").toURI())), "/") .containerize( Containerizer.to(TarImage.at(outputPath).named("ignored")) .setAllowInsecureRegistries(true)); - String toImage = "localhost:5000/basic-jibtar"; + String toImage = dockerHost + ":5000/basic-jibtar"; JibContainer jibContainer = Jib.from(TarImage.at(outputPath).named("ignored")) .setEntrypoint("cat", "/hello") @@ -203,7 +228,7 @@ public void testBasic_tarBaseImage_jibImageToDockerDaemon() // tar saved with Jib.from("busybox").addLayer(...("core/hello")).containerize(TarImage.at...) Path path = Paths.get(Resources.getResource("core/busybox-jib.tar").toURI()); - String toImage = "localhost:5000/basic-jibtar-to-docker"; + String toImage = dockerHost + ":5000/basic-jibtar-to-docker"; JibContainer jibContainer = Jib.from(TarImage.at(path).named("ignored")) .setEntrypoint("cat", "/hello") @@ -213,6 +238,7 @@ public void testBasic_tarBaseImage_jibImageToDockerDaemon() Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage)); Assert.assertEquals( "Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest())); + imageToDelete = toImage; } @Test @@ -221,7 +247,7 @@ public void testScratch_defaultPlatform() CacheDirectoryCreationException, InvalidImageReferenceException { Jib.fromScratch() .containerize( - Containerizer.to(RegistryImage.named("localhost:5000/jib-scratch:default-platform")) + Containerizer.to(RegistryImage.named(dockerHost + ":5000/jib-scratch:default-platform")) .setAllowInsecureRegistries(true)); V22ManifestTemplate manifestTemplate = @@ -245,7 +271,7 @@ public void testScratch_singlePlatform() Jib.fromScratch() .setPlatforms(ImmutableSet.of(new Platform("arm64", "windows"))) .containerize( - Containerizer.to(RegistryImage.named("localhost:5000/jib-scratch:single-platform")) + Containerizer.to(RegistryImage.named(dockerHost + ":5000/jib-scratch:single-platform")) .setAllowInsecureRegistries(true)); V22ManifestTemplate manifestTemplate = @@ -270,7 +296,7 @@ public void testScratch_multiPlatform() .setPlatforms( ImmutableSet.of(new Platform("arm64", "windows"), new Platform("amd32", "windows"))) .containerize( - Containerizer.to(RegistryImage.named("localhost:5000/jib-scratch:multi-platform")) + Containerizer.to(RegistryImage.named(dockerHost + ":5000/jib-scratch:multi-platform")) .setAllowInsecureRegistries(true)); V22ManifestListTemplate manifestList = @@ -287,6 +313,47 @@ public void testScratch_multiPlatform() Assert.assertEquals("windows", platform2.getOs()); } + @Test + public void testBasic_jibImageToDockerDaemon() + throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, + RegistryException, CacheDirectoryCreationException { + String toImage = dockerHost + ":5000/docker-to-docker"; + Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox")) + .setEntrypoint("echo", "Hello World") + .containerize(Containerizer.to(DockerDaemonImage.named(toImage))); + + String output = new Command("docker", "run", "--rm", toImage).run(); + Assert.assertEquals("Hello World\n", output); + imageToDelete = toImage; + } + + @Test + public void testDistroless_ociManifest() + throws IOException, InterruptedException, ExecutionException, RegistryException, + CacheDirectoryCreationException, InvalidImageReferenceException { + Jib.from("gcr.io/distroless/base@" + KNOWN_OCI_INDEX_SHA) + .setPlatforms( + ImmutableSet.of(new Platform("arm64", "linux"), new Platform("amd64", "linux"))) + .containerize( + Containerizer.to( + RegistryImage.named(dockerHost + ":5000/jib-distroless:multi-platform")) + .setAllowInsecureRegistries(true)); + + V22ManifestListTemplate manifestList = + (V22ManifestListTemplate) + distrolessRegistryClient.pullManifest("multi-platform").getManifest(); + Assert.assertEquals(2, manifestList.getManifests().size()); + ManifestDescriptorTemplate.Platform platform1 = + manifestList.getManifests().get(0).getPlatform(); + ManifestDescriptorTemplate.Platform platform2 = + manifestList.getManifests().get(1).getPlatform(); + + Assert.assertEquals("arm64", platform1.getArchitecture()); + Assert.assertEquals("linux", platform1.getOs()); + Assert.assertEquals("amd64", platform2.getArchitecture()); + Assert.assertEquals("linux", platform2.getOs()); + } + @Test public void testOffline() throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, @@ -294,7 +361,7 @@ public void testOffline() Path cacheDirectory = temporaryFolder.getRoot().toPath(); JibContainerBuilder jibContainerBuilder = - Jib.from("localhost:5000/busybox").setEntrypoint("echo", "Hello World"); + Jib.from(dockerHost + ":5000/busybox").setEntrypoint("echo", "Hello World"); // Should fail since Jib can't build to registry offline try { @@ -314,7 +381,9 @@ public void testOffline() Assert.fail(); } catch (ExecutionException ex) { Assert.assertEquals( - "Cannot run Jib in offline mode; localhost:5000/busybox not found in local Jib cache", + "Cannot run Jib in offline mode; " + + dockerHost + + ":5000/busybox not found in local Jib cache", ex.getCause().getMessage()); } @@ -326,13 +395,13 @@ public void testOffline() // Run again in offline mode, should succeed this time jibContainerBuilder.containerize( - Containerizer.to(DockerDaemonImage.named("localhost:5000/offline")) + Containerizer.to(DockerDaemonImage.named(dockerHost + ":5000/offline")) .setBaseImageLayersCache(cacheDirectory) .setOfflineMode(true)); // Verify output Assert.assertEquals( - "Hello World\n", new Command("docker", "run", "--rm", "localhost:5000/offline").run()); + "Hello World\n", new Command("docker", "run", "--rm", dockerHost + ":5000/offline").run()); } /** Ensure that a provided executor is not disposed. */ @@ -344,7 +413,7 @@ public void testProvidedExecutorNotDisposed() try { Jib.fromScratch() .containerize( - Containerizer.to(RegistryImage.named("localhost:5000/foo")) + Containerizer.to(RegistryImage.named(dockerHost + ":5000/foo")) .setExecutorService(executorService) .setAllowInsecureRegistries(true)); Assert.assertFalse(executorService.isShutdown()); diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibMultiPlatformIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibMultiPlatformIntegrationTest.java new file mode 100644 index 0000000000..df00fe04d0 --- /dev/null +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibMultiPlatformIntegrationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2024 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.api; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.tools.jib.Command; +import com.google.cloud.tools.jib.api.buildplan.Platform; +import com.google.cloud.tools.jib.registry.LocalRegistry; +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import org.junit.After; +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; + +public class JibMultiPlatformIntegrationTest { + + @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000); + + private final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; + private String imageToDelete; + + @After + public void tearDown() throws IOException, InterruptedException { + System.clearProperty("sendCredentialsOverHttp"); + if (imageToDelete != null) { + new Command("docker", "rmi", imageToDelete).run(); + } + } + + @Test + public void testBasic_jibImageToDockerDaemon_arm64() + throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, + RegistryException, CacheDirectoryCreationException { + // Use arm64v8/busybox as base image. + String toImage = dockerHost + ":5000/docker-daemon-mismatched-arch"; + Jib.from( + RegistryImage.named( + "busybox@sha256:eb427d855f82782c110b48b9a398556c629ce4951ae252c6f6751a136e194668")) + .containerize(Containerizer.to(DockerDaemonImage.named(toImage))); + String os = + new Command("docker", "inspect", toImage, "--format", "{{.Os}}").run().replace("\n", ""); + String architecture = + new Command("docker", "inspect", toImage, "--format", "{{.Architecture}}") + .run() + .replace("\n", ""); + assertThat(os).isEqualTo("linux"); + assertThat(architecture).isEqualTo("arm64"); + imageToDelete = toImage; + } + + @Test + public void testBasicMultiPlatform_toDockerDaemon_pickFirstPlatformWhenNoMatchingImage() + throws IOException, InterruptedException, InvalidImageReferenceException, + CacheDirectoryCreationException, ExecutionException, RegistryException { + String toImage = dockerHost + ":5000/docker-daemon-multi-plat-mismatched-configs"; + Jib.from( + RegistryImage.named( + "busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977")) + .setPlatforms(ImmutableSet.of(new Platform("s390x", "linux"), new Platform("arm", "linux"))) + .containerize( + Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true)); + String os = + new Command("docker", "inspect", toImage, "--format", "{{.Os}}").run().replace("\n", ""); + String architecture = + new Command("docker", "inspect", toImage, "--format", "{{.Architecture}}") + .run() + .replace("\n", ""); + assertThat(os).isEqualTo("linux"); + assertThat(architecture).isEqualTo("s390x"); + imageToDelete = toImage; + } + + @Test + public void testBasicMultiPlatform_toDockerDaemon() + throws IOException, InterruptedException, ExecutionException, RegistryException, + CacheDirectoryCreationException, InvalidImageReferenceException { + String toImage = dockerHost + ":5000/docker-daemon-multi-platform"; + Jib.from( + RegistryImage.named( + "busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977")) + .setPlatforms( + ImmutableSet.of(new Platform("arm64", "linux"), new Platform("amd64", "linux"))) + .setEntrypoint("echo", "Hello World") + .containerize( + Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true)); + + String output = new Command("docker", "run", "--rm", toImage).run(); + Assert.assertEquals("Hello World\n", output); + imageToDelete = toImage; + } +} diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java index 1c194b2198..9351974634 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/ReproducibleImageTest.java @@ -102,9 +102,9 @@ public void testTarballStructure() throws IOException { assertThat(actual) .containsExactly( - "c46572ef74f58d95e44dd36c1fbdfebd3752e8b56a794a13c11cfed35a1a6e1c.tar.gz", - "6d2763b0f3940d324ea6b55386429e5b173899608abf7d1bff62e25dd2e4dcea.tar.gz", - "530c1954a2b087d0b989895ea56435c9dc739a973f2d2b6cb9bb98e55bbea7ac.tar.gz", + "98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz", + "527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz", + "16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz", "config.json", "manifest.json") .inOrder(); @@ -114,7 +114,7 @@ public void testTarballStructure() throws IOException { public void testManifest() throws IOException { String expectedManifest = "[{\"Config\":\"config.json\",\"RepoTags\":[\"jib-core/reproducible:latest\"]," - + "\"Layers\":[\"c46572ef74f58d95e44dd36c1fbdfebd3752e8b56a794a13c11cfed35a1a6e1c.tar.gz\",\"6d2763b0f3940d324ea6b55386429e5b173899608abf7d1bff62e25dd2e4dcea.tar.gz\",\"530c1954a2b087d0b989895ea56435c9dc739a973f2d2b6cb9bb98e55bbea7ac.tar.gz\"]}]"; + + "\"Layers\":[\"98682a867906d9d07cf3c51a4fb9e08e9d5baddd1ca5dc7834f58f434c9cb15c.tar.gz\",\"527db49d4e0c4159346119b4971d59016bfedceed874abab2b510ce433f6b15c.tar.gz\",\"16d03883198935b4119896dcea0ea14e1bf105b6ac0a35a88820d08bc0263306.tar.gz\"]}]"; String generatedManifest = extractFromTarFileAsString(imageTar, "manifest.json"); assertThat(generatedManifest).isEqualTo(expectedManifest); } @@ -125,7 +125,7 @@ public void testConfiguration() throws IOException { "{\"created\":\"1970-01-01T00:00:00Z\",\"architecture\":\"amd64\",\"os\":\"linux\"," + "\"config\":{\"Env\":[],\"Entrypoint\":[\"echo\",\"Hello World\"],\"ExposedPorts\":{},\"Labels\":{},\"Volumes\":{}}," + "\"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\":\"\"}]," - + "\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:18e4f44e6d1835bd968339b166057bd17ab7d4cbb56dc7262a5cafea7cf8d405\",\"sha256:13369c34f073f2b9c1fa6431e23d925f1a8eac65b1726c8cc8fcc2596c69b414\",\"sha256:4f92c507112d7880ca0f504ef8272b7fdee107263270125036a260a741565923\"]}}"; + + "\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:2fcc2157bf42c89195676ef6e973a96d7b018c9d30ba89db95e9e0722e1c8ef3\",\"sha256:21f521f3217067d277af37512a08c72281d90fdd02d7174db632c8c3a34403bd\",\"sha256:6beba018395265af5061864b7f4678e831eb2daebb1045487c641fc8b142e319\"]}}"; String generatedConfig = extractFromTarFileAsString(imageTar, "config.json"); assertThat(generatedConfig).isEqualTo(expectedConfig); } diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobCheckerIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobCheckerIntegrationTest.java index 3c1cef4e25..508fbd51ee 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobCheckerIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobCheckerIntegrationTest.java @@ -37,7 +37,10 @@ public void testCheck_exists() throws IOException, RegistryException { RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient) .newRegistryClient(); V22ManifestTemplate manifestTemplate = - registryClient.pullManifest("latest", V22ManifestTemplate.class).getManifest(); + registryClient + .pullManifest( + ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class) + .getManifest(); DescriptorDigest blobDigest = manifestTemplate.getLayers().get(0).getDigest(); Assert.assertEquals(blobDigest, registryClient.checkBlob(blobDigest).get().getDigest()); diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPullerIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPullerIntegrationTest.java index 6a64fbffc5..1886e4c649 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPullerIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPullerIntegrationTest.java @@ -42,7 +42,10 @@ public void testPull() throws IOException, RegistryException { RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient) .newRegistryClient(); V22ManifestTemplate manifestTemplate = - registryClient.pullManifest("latest", V22ManifestTemplate.class).getManifest(); + registryClient + .pullManifest( + ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class) + .getManifest(); DescriptorDigest realDigest = manifestTemplate.getLayers().get(0).getDigest(); diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPusherIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPusherIntegrationTest.java index 7a82c0da48..3d68e2ef63 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPusherIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/BlobPusherIntegrationTest.java @@ -34,6 +34,8 @@ public class BlobPusherIntegrationTest { @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000); private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {}); + private final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; @Test public void testPush() throws DigestException, IOException, RegistryException { @@ -44,7 +46,7 @@ public void testPush() throws DigestException, IOException, RegistryException { "52a9e4d4ba4333ce593707f98564fee1e6d898db0d3602408c0b2a6a424d357c"); RegistryClient registryClient = - RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "testimage", httpClient) + RegistryClient.factory(EventHandlers.NONE, dockerHost + ":5000", "testimage", httpClient) .newRegistryClient(); Assert.assertFalse(registryClient.pushBlob(testBlobDigest, testBlob, null, ignored -> {})); } diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/LocalRegistry.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/LocalRegistry.java index 7a75907b15..c6cb6a16c8 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/LocalRegistry.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/LocalRegistry.java @@ -26,8 +26,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import org.junit.rules.ExternalResource; @@ -37,6 +41,8 @@ public class LocalRegistry extends ExternalResource { private final String containerName = "registry-" + UUID.randomUUID(); + public final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; private final int port; @Nullable private final String username; @Nullable private final String password; @@ -74,16 +80,25 @@ public void start() throws IOException, InterruptedException { // BCrypt generates hashes using $2a$ algorithm (instead of $2y$ from docs), but this seems // to work okay String credentialString = username + ":" + BCrypt.hashpw(password, BCrypt.gensalt()); - Path tempFolder = Files.createTempDirectory(Paths.get("/tmp"), ""); + FileAttribute> attrs = + PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x")); + Path tempFolder = Files.createTempDirectory(Paths.get("/tmp"), "", attrs); Files.write( tempFolder.resolve("htpasswd"), credentialString.getBytes(StandardCharsets.UTF_8)); - + String authenticationVolume = tempFolder + ":/auth"; + if (System.getenv("KOKORO_JOB_CLUSTER") != null + && System.getenv("KOKORO_JOB_CLUSTER").equals("MACOS_EXTERNAL")) { + authenticationVolume = "/home/docker/auth:/auth"; + } else if (System.getenv("KOKORO_JOB_CLUSTER") != null + && System.getenv("KOKORO_JOB_CLUSTER").equals("GCP_UBUNTU_DOCKER")) { + authenticationVolume = "/tmpfs/auth:/auth"; + } // Run the Docker registry dockerTokens.addAll( Arrays.asList( "-v", // Volume mount used for storing credentials - tempFolder + ":/auth", + authenticationVolume, "-e", "REGISTRY_AUTH=htpasswd", "-e", @@ -131,26 +146,26 @@ public void pull(String from) throws IOException, InterruptedException { public void pullAndPushToLocal(String from, String to) throws IOException, InterruptedException { login(); new Command("docker", "pull", from).run(); - new Command("docker", "tag", from, "localhost:" + port + "/" + to).run(); - new Command("docker", "push", "localhost:" + port + "/" + to).run(); + new Command("docker", "tag", from, dockerHost + ":" + port + "/" + to).run(); + new Command("docker", "push", dockerHost + ":" + port + "/" + to).run(); logout(); } private void login() throws IOException, InterruptedException { if (username != null && password != null) { - new Command("docker", "login", "localhost:" + port, "-u", username, "--password-stdin") + new Command("docker", "login", dockerHost + ":" + port, "-u", username, "--password-stdin") .run(password.getBytes(StandardCharsets.UTF_8)); } } private void logout() throws IOException, InterruptedException { if (username != null && password != null) { - new Command("docker", "logout", "localhost:" + port).run(); + new Command("docker", "logout", dockerHost + ":" + port).run(); } } private void waitUntilReady() throws InterruptedException, MalformedURLException { - URL queryUrl = new URL("http://localhost:" + port + "/v2/_catalog"); + URL queryUrl = new URL("http://" + dockerHost + ":" + port + "/v2/_catalog"); for (int i = 0; i < 40; i++) { try { diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestCheckerIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestCheckerIntegrationTest.java index a7d80d830f..fa8d5c7a38 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestCheckerIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestCheckerIntegrationTest.java @@ -30,7 +30,7 @@ /** Integration tests for {@link ManifestChecker}. */ public class ManifestCheckerIntegrationTest { - /** A known manifest list sha for openjdk:11-jre-slim. */ + /** A known manifest list sha for gcr.io/distroless/base. */ private static final String KNOWN_MANIFEST = "sha256:44cbdb9c24e123882d7894ba78fb6f572d2496889885a47eb4b32241a8c07a00"; diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPullerIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPullerIntegrationTest.java index 4d69dd8012..da6f058ccb 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPullerIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPullerIntegrationTest.java @@ -16,16 +16,18 @@ package com.google.cloud.tools.jib.registry; +import static com.google.common.truth.Truth.assertThat; + import com.google.cloud.tools.jib.api.RegistryException; import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.http.FailoverHttpClient; import com.google.cloud.tools.jib.image.json.ManifestTemplate; +import com.google.cloud.tools.jib.image.json.OciIndexTemplate; +import com.google.cloud.tools.jib.image.json.OciManifestTemplate; import com.google.cloud.tools.jib.image.json.V21ManifestTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestTemplate; import java.io.IOException; -import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.ClassRule; @@ -38,7 +40,21 @@ public class ManifestPullerIntegrationTest { public static final String KNOWN_MANIFEST_LIST_SHA = "sha256:44cbdb9c24e123882d7894ba78fb6f572d2496889885a47eb4b32241a8c07a00"; + /** A known OCI image index sha for gcr.io/distroless/base. */ + public static final String KNOWN_OCI_INDEX_SHA = + "sha256:2c50b819aa3bfaf6ae72e47682f6c5abc0f647cf3f4224a4a9be97dd30433909"; + + /** A known docker manifest schema 2 sha for gcr.io/distroless/base. */ + public static final String KNOWN_MANIFEST_V22_SHA = + "sha256:da5c568e59f3241b09e5699a525a37b3309ce2c182d8d20802b9eaee55711b19"; + + /** A known oci manifest sha for gcr.io/distroless/base. */ + public static final String KNOWN_OCI_MANIFEST_SHA = + "sha256:0477dc38b254096e350a9b605b7355d3cf0d5a844558e6986148ce2a1fe18ba8"; + @ClassRule public static LocalRegistry localRegistry = new LocalRegistry(5000); + public final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; @BeforeClass public static void setUp() throws IOException, InterruptedException { @@ -50,26 +66,44 @@ public static void setUp() throws IOException, InterruptedException { @Test public void testPull_v21() throws IOException, RegistryException { RegistryClient registryClient = - RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "busybox", httpClient) + RegistryClient.factory(EventHandlers.NONE, dockerHost + ":5000", "busybox", httpClient) .newRegistryClient(); V21ManifestTemplate manifestTemplate = registryClient.pullManifest("latest", V21ManifestTemplate.class).getManifest(); - Assert.assertEquals(1, manifestTemplate.getSchemaVersion()); - Assert.assertTrue(manifestTemplate.getFsLayers().size() > 0); + assertThat(manifestTemplate.getSchemaVersion()).isEqualTo(1); + assertThat(manifestTemplate.getFsLayers()).isNotEmpty(); } @Test public void testPull_v22() throws IOException, RegistryException { RegistryClient registryClient = - RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/java", httpClient) + RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient) .newRegistryClient(); - ManifestTemplate manifestTemplate = registryClient.pullManifest("latest").getManifest(); - Assert.assertEquals(2, manifestTemplate.getSchemaVersion()); - V22ManifestTemplate v22ManifestTemplate = (V22ManifestTemplate) manifestTemplate; - Assert.assertTrue(v22ManifestTemplate.getLayers().size() > 0); + V22ManifestTemplate manifestTemplate = + registryClient + .pullManifest(KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class) + .getManifest(); + + assertThat(manifestTemplate.getSchemaVersion()).isEqualTo(2); + assertThat(manifestTemplate.getLayers()).isNotEmpty(); + } + + @Test + public void testPull_ociManifest() throws IOException, RegistryException { + RegistryClient registryClient = + RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient) + .newRegistryClient(); + + OciManifestTemplate manifestTemplate = + registryClient + .pullManifest(KNOWN_OCI_MANIFEST_SHA, OciManifestTemplate.class) + .getManifest(); + + assertThat(manifestTemplate.getSchemaVersion()).isEqualTo(2); + assertThat(manifestTemplate.getLayers()).isNotEmpty(); } @Test @@ -78,47 +112,55 @@ public void testPull_v22ManifestList() throws IOException, RegistryException { RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient) .newRegistryClient(); - // Ensure ":latest" is a manifest list + // Ensures call to image at the specified SHA returns a manifest list V22ManifestListTemplate manifestListTargeted = - registryClient.pullManifest("latest", V22ManifestListTemplate.class).getManifest(); - Assert.assertEquals(2, manifestListTargeted.getSchemaVersion()); - Assert.assertTrue(manifestListTargeted.getManifests().size() > 0); - - // Generic call to ":latest" pulls a manifest list - ManifestTemplate manifestListGeneric = registryClient.pullManifest("latest").getManifest(); - Assert.assertEquals(2, manifestListGeneric.getSchemaVersion()); - MatcherAssert.assertThat( - manifestListGeneric, CoreMatchers.instanceOf(V22ManifestListTemplate.class)); - Assert.assertTrue(((V22ManifestListTemplate) manifestListGeneric).getManifests().size() > 0); - - // Referencing a manifest list by sha256, should return a manifest list - ManifestTemplate manifestListSha256 = + registryClient + .pullManifest(KNOWN_MANIFEST_LIST_SHA, V22ManifestListTemplate.class) + .getManifest(); + assertThat(manifestListTargeted.getSchemaVersion()).isEqualTo(2); + assertThat(manifestListTargeted.getManifests()).isNotEmpty(); + + // Generic call to image at the specified SHA, should also return a manifest list + ManifestTemplate manifestListGeneric = registryClient.pullManifest(KNOWN_MANIFEST_LIST_SHA).getManifest(); - Assert.assertEquals(2, manifestListSha256.getSchemaVersion()); - MatcherAssert.assertThat( - manifestListSha256, CoreMatchers.instanceOf(V22ManifestListTemplate.class)); - Assert.assertTrue(((V22ManifestListTemplate) manifestListSha256).getManifests().size() > 0); - - // Call to ":latest" targeting a manifest pulls a manifest. - V22ManifestTemplate manifestTargeted = - registryClient.pullManifest("latest", V22ManifestTemplate.class).getManifest(); - Assert.assertEquals(2, manifestTargeted.getSchemaVersion()); + assertThat(manifestListGeneric.getSchemaVersion()).isEqualTo(2); + assertThat(manifestListGeneric).isInstanceOf(V22ManifestListTemplate.class); + assertThat(((V22ManifestListTemplate) manifestListGeneric).getManifests()).isNotEmpty(); + } + + @Test + public void testPull_ociIndex() throws IOException, RegistryException { + RegistryClient registryClient = + RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient) + .newRegistryClient(); + + // Ensures call to image at the specified SHA returns an OCI index + OciIndexTemplate manifestListTargeted = + registryClient.pullManifest(KNOWN_OCI_INDEX_SHA, OciIndexTemplate.class).getManifest(); + assertThat(manifestListTargeted.getSchemaVersion()).isEqualTo(2); + assertThat((manifestListTargeted.getManifests().size() > 0)).isTrue(); + + // Generic call to image at the specified SHA, should also return an OCI index + ManifestTemplate manifestListGeneric = + registryClient.pullManifest(KNOWN_OCI_INDEX_SHA).getManifest(); + assertThat(manifestListGeneric.getSchemaVersion()).isEqualTo(2); + assertThat(manifestListGeneric).isInstanceOf(OciIndexTemplate.class); + assertThat(((OciIndexTemplate) manifestListGeneric).getManifests()).isNotEmpty(); } @Test public void testPull_unknownManifest() throws RegistryException, IOException { try { RegistryClient registryClient = - RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "busybox", httpClient) + RegistryClient.factory(EventHandlers.NONE, dockerHost + ":5000", "busybox", httpClient) .newRegistryClient(); registryClient.pullManifest("nonexistent-tag"); Assert.fail("Trying to pull nonexistent image should have errored"); } catch (RegistryErrorException ex) { - MatcherAssert.assertThat( - ex.getMessage(), - CoreMatchers.containsString( - "pull image manifest for localhost:5000/busybox:nonexistent-tag")); + assertThat(ex) + .hasMessageThat() + .contains("pull image manifest for " + dockerHost + ":5000/busybox:nonexistent-tag"); } } } diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java index a6f48dc76d..478762d893 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java @@ -39,6 +39,8 @@ public class ManifestPusherIntegrationTest { @ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000); private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {}); + public final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; @Test public void testPush_missingBlobs() throws IOException, RegistryException { @@ -48,7 +50,7 @@ public void testPush_missingBlobs() throws IOException, RegistryException { ManifestTemplate manifestTemplate = registryClient.pullManifest("latest").getManifest(); registryClient = - RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "ignored", httpClient) + RegistryClient.factory(EventHandlers.NONE, dockerHost + ":5000", "ignored", httpClient) .newRegistryClient(); try { registryClient.pushManifest(manifestTemplate, "latest"); @@ -81,7 +83,7 @@ public void testPush() throws DigestException, IOException, RegistryException { // Pushes the BLOBs. RegistryClient registryClient = - RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "testimage", httpClient) + RegistryClient.factory(EventHandlers.NONE, dockerHost + ":5000", "testimage", httpClient) .newRegistryClient(); Assert.assertFalse( registryClient.pushBlob(testLayerBlobDigest, testLayerBlob, null, ignored -> {})); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/Containerizer.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/Containerizer.java index 8ebbb4d957..96442a5135 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/Containerizer.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/Containerizer.java @@ -20,7 +20,8 @@ import com.google.cloud.tools.jib.builder.steps.StepsRunner; import com.google.cloud.tools.jib.configuration.BuildContext; import com.google.cloud.tools.jib.configuration.ImageConfiguration; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.docker.CliDockerClient; +import com.google.cloud.tools.jib.docker.DockerClientResolver; import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.filesystem.XdgDirectories; import com.google.common.base.Preconditions; @@ -90,17 +91,14 @@ public static Containerizer to(RegistryImage registryImage) { * @return a new {@link Containerizer} */ public static Containerizer to(DockerDaemonImage dockerDaemonImage) { - ImageConfiguration imageConfiguration = - ImageConfiguration.builder(dockerDaemonImage.getImageReference()).build(); - DockerClient dockerClient = - new DockerClient( - dockerDaemonImage.getDockerExecutable(), dockerDaemonImage.getDockerEnvironment()); - Function stepsRunnerFactory = - buildContext -> StepsRunner.begin(buildContext).dockerLoadSteps(dockerClient); + DockerClientResolver.resolve(dockerDaemonImage.getDockerEnvironment()) + .orElse( + new CliDockerClient( + dockerDaemonImage.getDockerExecutable(), + dockerDaemonImage.getDockerEnvironment())); - return new Containerizer( - DESCRIPTION_FOR_DOCKER_DAEMON, imageConfiguration, stepsRunnerFactory, false); + return to(dockerClient, dockerDaemonImage); } /** @@ -127,6 +125,24 @@ public static Containerizer to(TarImage tarImage) { DESCRIPTION_FOR_TARBALL, imageConfiguration, stepsRunnerFactory, false); } + /** + * Gets a new {@link Containerizer} that containerizes to a Docker daemon. + * + * @param dockerClient the {@link DockerClient} to connect + * @param dockerDaemonImage the {@link DockerDaemonImage} that defines target Docker daemon + * @return a new {@link Containerizer} + */ + public static Containerizer to(DockerClient dockerClient, DockerDaemonImage dockerDaemonImage) { + ImageConfiguration imageConfiguration = + ImageConfiguration.builder(dockerDaemonImage.getImageReference()).build(); + + Function stepsRunnerFactory = + buildContext -> StepsRunner.begin(buildContext).dockerLoadSteps(dockerClient); + + return new Containerizer( + DESCRIPTION_FOR_DOCKER_DAEMON, imageConfiguration, stepsRunnerFactory, false); + } + private final String description; private final ImageConfiguration imageConfiguration; private final Function stepsRunnerFactory; diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java new file mode 100644 index 0000000000..7e08a79495 --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.api; + +import com.google.cloud.tools.jib.image.ImageTarball; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Consumer; + +public interface DockerClient { + + /** + * Validate if the DockerClient is supported. + * + * @param parameters to be used by the docker client + * @return true if conditions are met + */ + boolean supported(Map parameters); + + /** + * Loads an image tarball into the Docker daemon. + * + * @see https://docs.docker.com/engine/reference/commandline/load + * @param imageTarball the built container tarball + * @param writtenByteCountListener callback to call when bytes are loaded + * @return stdout from {@code docker} + * @throws InterruptedException if the 'docker load' process is interrupted + * @throws IOException if streaming the blob to 'docker load' fails + */ + String load(ImageTarball imageTarball, Consumer writtenByteCountListener) + throws InterruptedException, IOException; + + /** + * Saves an image tarball from the Docker daemon. + * + * @see https://docs.docker.com/engine/reference/commandline/save + * @param imageReference the image to save + * @param outputPath the destination path to save the output tarball + * @param writtenByteCountListener callback to call when bytes are saved + * @throws InterruptedException if the 'docker save' process is interrupted + * @throws IOException if creating the tarball fails + */ + void save(ImageReference imageReference, Path outputPath, Consumer writtenByteCountListener) + throws InterruptedException, IOException; + + /** + * Gets the size, image ID, and diff IDs of an image in the Docker daemon. + * + * @param imageReference the image to inspect + * @return the size, image ID, and diff IDs of the image + * @throws IOException if an I/O exception occurs or {@code docker inspect} failed + * @throws InterruptedException if the {@code docker inspect} process was interrupted + */ + ImageDetails inspect(ImageReference imageReference) throws IOException, InterruptedException; + + /** + * Gets docker info details of local docker installation. + * + * @return docker info details. + * @throws IOException if an I/O exception occurs or {@code docker info} failed + * @throws InterruptedException if the {@code docker info} process was interrupted + */ + default DockerInfoDetails info() throws IOException, InterruptedException { + return new DockerInfoDetails(); + } +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerDaemonImage.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerDaemonImage.java index a97e5afbad..d6fcde1864 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerDaemonImage.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerDaemonImage.java @@ -16,7 +16,7 @@ package com.google.cloud.tools.jib.api; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.docker.CliDockerClient; import java.nio.file.Path; import java.util.Collections; import java.util.Map; @@ -49,7 +49,7 @@ public static DockerDaemonImage named(String imageReference) } private final ImageReference imageReference; - private Path dockerExecutable = DockerClient.DEFAULT_DOCKER_CLIENT; + private Path dockerExecutable = CliDockerClient.DEFAULT_DOCKER_CLIENT; private Map dockerEnvironment = Collections.emptyMap(); /** Instantiate with {@link #named}. */ diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java new file mode 100644 index 0000000000..fa486c40f9 --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.api; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.cloud.tools.jib.json.JsonTemplate; + +/** Contains docker info details outputted by {@code docker info}. */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class DockerInfoDetails implements JsonTemplate { + + @JsonProperty("OSType") + private String osType = ""; + + @JsonProperty("Architecture") + private String architecture = ""; + + public String getOsType() { + return osType; + } + + public String getArchitecture() { + return architecture; + } +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageDetails.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageDetails.java new file mode 100644 index 0000000000..c1c90766e1 --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageDetails.java @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.api; + +import java.security.DigestException; +import java.util.List; + +public interface ImageDetails { + + long getSize(); + + DescriptorDigest getImageId() throws DigestException; + + List getDiffIds() throws DigestException; +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/Jib.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/Jib.java index 20e70bb638..a8387df0e6 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/Jib.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/Jib.java @@ -106,5 +106,19 @@ public static JibContainerBuilder fromScratch() { return from(ImageReference.scratch()); } + /** + * Starts building the container from a base image stored in the Docker cache. Requires a running + * Docker daemon. + * + * @param dockerClient the {@link DockerClient} to connect + * @param dockerDaemonImage the {@link DockerDaemonImage} that defines the base image and Docker + * client + * @return a new {@link JibContainerBuilder} to continue building the container + */ + public static JibContainerBuilder from( + DockerClient dockerClient, DockerDaemonImage dockerDaemonImage) { + return new JibContainerBuilder(dockerClient, dockerDaemonImage); + } + private Jib() {} } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java index bf41b2eb7e..aeb50913d8 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java @@ -29,17 +29,20 @@ public class JibContainer { private final DescriptorDigest imageDigest; private final DescriptorDigest imageId; private final Set tags; + private final boolean imagePushed; @VisibleForTesting JibContainer( ImageReference targetImage, DescriptorDigest imageDigest, DescriptorDigest imageId, - Set tags) { + Set tags, + boolean imagePushed) { this.targetImage = targetImage; this.imageDigest = imageDigest; this.imageId = imageId; this.tags = tags; + this.imagePushed = imagePushed; } static JibContainer from(BuildContext buildContext, BuildResult buildResult) { @@ -47,7 +50,7 @@ static JibContainer from(BuildContext buildContext, BuildResult buildResult) { DescriptorDigest imageDigest = buildResult.getImageDigest(); DescriptorDigest imageId = buildResult.getImageId(); Set tags = buildContext.getAllTargetImageTags(); - return new JibContainer(targetImage, imageDigest, imageId, tags); + return new JibContainer(targetImage, imageDigest, imageId, tags, buildResult.isImagePushed()); } /** @@ -59,6 +62,15 @@ public ImageReference getTargetImage() { return targetImage; } + /** + * Returns true if we pushed this image all the way to a registry. + * + * @return true if pushed. + */ + public boolean isImagePushed() { + return imagePushed; + } + /** * Gets the digest of the registry image manifest built by Jib. This digest can be used to fetch a * specific image from the registry in the form {@code myregistry/myimage@digest}. @@ -89,7 +101,7 @@ public Set getTags() { @Override public int hashCode() { - return Objects.hash(targetImage, imageDigest, imageId, tags); + return Objects.hash(targetImage, imageDigest, imageId, tags, imagePushed); } @Override @@ -104,6 +116,7 @@ public boolean equals(Object other) { return targetImage.equals(otherContainer.targetImage) && imageDigest.equals(otherContainer.imageDigest) && imageId.equals(otherContainer.imageId) - && tags.equals(otherContainer.tags); + && tags.equals(otherContainer.tags) + && imagePushed == otherContainer.imagePushed; } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java index 3946603418..9a809ef798 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java @@ -29,7 +29,8 @@ import com.google.cloud.tools.jib.configuration.BuildContext; import com.google.cloud.tools.jib.configuration.ContainerConfiguration; import com.google.cloud.tools.jib.configuration.ImageConfiguration; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.docker.CliDockerClient; +import com.google.cloud.tools.jib.docker.DockerClientResolver; import com.google.cloud.tools.jib.event.EventHandlers; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Verify; @@ -100,7 +101,10 @@ private static String capitalizeFirstLetter(String string) { this( ImageConfiguration.builder(baseImage.getImageReference()) .setDockerClient( - new DockerClient(baseImage.getDockerExecutable(), baseImage.getDockerEnvironment())) + DockerClientResolver.resolve(baseImage.getDockerEnvironment()) + .orElse( + new CliDockerClient( + baseImage.getDockerExecutable(), baseImage.getDockerEnvironment()))) .build(), BuildContext.builder()); } @@ -115,6 +119,15 @@ private static String capitalizeFirstLetter(String string) { BuildContext.builder()); } + /** Instantiate with {@link Jib#from}. */ + JibContainerBuilder(DockerClient dockerClient, DockerDaemonImage baseImage) { + this( + ImageConfiguration.builder(baseImage.getImageReference()) + .setDockerClient(dockerClient) + .build(), + BuildContext.builder()); + } + @VisibleForTesting JibContainerBuilder( ImageConfiguration imageConfiguration, BuildContext.Builder buildContextBuilder) { diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/MainClassFinder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/MainClassFinder.java index 5967f8a6bc..edd452ef54 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/MainClassFinder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/MainClassFinder.java @@ -168,6 +168,14 @@ public static Result find(List files, Consumer logger) { mainClasses.add(reader.getClassName().replace('/', '.')); } + } catch (IllegalArgumentException ex) { + throw new UnsupportedOperationException( + "Check the full stace trace, and if the root cause is from ASM ClassReader about " + + "unsupported class file version, see " + + "https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md" + + "#i-am-seeing-unsupported-class-file-major-version-when-building", + ex); + } catch (ArrayIndexOutOfBoundsException ignored) { // Not a valid class file (thrown by ClassReader if it reads an invalid format) logger.accept(LogEvent.warn("Invalid class file found: " + file)); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildResult.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildResult.java index ffc3f32232..570152f4e6 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildResult.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/BuildResult.java @@ -46,15 +46,17 @@ static BuildResult fromImage(Image image, Class retrieveDockerDaemonLayersStep( new TimerEventDispatcher( buildContext.getEventHandlers(), "Saving " + imageReference + " from Docker daemon")) { - DockerClient.DockerImageDetails dockerImageDetails = dockerClient.inspect(imageReference); + ImageDetails dockerImageDetails = dockerClient.inspect(imageReference); Optional cachedImage = getCachedDockerImage(buildContext.getBaseImageLayersCache(), dockerImageDetails); if (cachedImage.isPresent()) { @@ -119,7 +119,7 @@ static Callable retrieveDockerDaemonLayersStep( } Path tarPath = tempDirectoryProvider.newDirectory().resolve("out.tar"); - long size = dockerClient.inspect(imageReference).getSize(); + long size = dockerImageDetails.getSize(); try (ProgressEventDispatcher dockerProgress = progressEventDispatcher .newChildProducer() @@ -176,8 +176,7 @@ static Callable returnImageAndRegistryClientStep( } @VisibleForTesting - static Optional getCachedDockerImage( - Cache cache, DockerImageDetails dockerImageDetails) + static Optional getCachedDockerImage(Cache cache, ImageDetails dockerImageDetails) throws DigestException, IOException, CacheCorruptedException { // Get config Optional cachedConfig = @@ -216,12 +215,15 @@ static LocalImage cacheDockerImageTar( "Extracting tar " + tarPath + " into " + destination)) { TarExtractor.extract(tarPath, destination); - InputStream manifestStream = Files.newInputStream(destination.resolve("manifest.json")); - DockerManifestEntryTemplate loadManifest = - new ObjectMapper() - .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) - .readValue(manifestStream, DockerManifestEntryTemplate[].class)[0]; - manifestStream.close(); + DockerManifestEntryTemplate loadManifest; + try (InputStream manifestStream = + Files.newInputStream(destination.resolve("manifest.json"))) { + loadManifest = + JsonMapper.builder() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .build() + .readValue(manifestStream, DockerManifestEntryTemplate[].class)[0]; + } Path configPath = destination.resolve(loadManifest.getConfig()); ContainerConfigurationTemplate configurationTemplate = diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java index 04819903b6..6f58544b73 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java @@ -16,11 +16,10 @@ package com.google.cloud.tools.jib.builder.steps; -import com.google.cloud.tools.jib.api.LogEvent; import com.google.cloud.tools.jib.api.buildplan.Platform; import com.google.cloud.tools.jib.configuration.BuildContext; -import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate; +import com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException; import com.google.common.base.Verify; import java.nio.file.Path; import java.util.Optional; @@ -38,8 +37,8 @@ private PlatformChecker() {} * @param containerConfig container configuration JSON of the base image */ static void checkManifestPlatform( - BuildContext buildContext, ContainerConfigurationTemplate containerConfig) { - EventHandlers eventHandlers = buildContext.getEventHandlers(); + BuildContext buildContext, ContainerConfigurationTemplate containerConfig) + throws PlatformNotFoundInBaseImageException { Optional path = buildContext.getBaseImageConfiguration().getTarPath(); String baseImageName = path.map(Path::toString) @@ -49,9 +48,11 @@ static void checkManifestPlatform( Verify.verify(!platforms.isEmpty()); if (platforms.size() != 1) { - eventHandlers.dispatch( - LogEvent.warn( - "platforms configured, but '" + baseImageName + "' is not a manifest list")); + String msg = + String.format( + "cannot build for multiple platforms since the base image '%s' is not a manifest list.", + baseImageName); + throw new PlatformNotFoundInBaseImageException(msg); } else { Platform platform = platforms.iterator().next(); if (!platform.getArchitecture().equals(containerConfig.getArchitecture()) @@ -60,18 +61,15 @@ static void checkManifestPlatform( // Unfortunately, "platforms" has amd64/linux by default even if the user didn't explicitly // configure it. Skip reporting to suppress false alarm. if (!(platform.getArchitecture().equals("amd64") && platform.getOs().equals("linux"))) { - String warning = - "the configured platform (%s/%s) doesn't match the platform (%s/%s) of the base " - + "image (%s)"; - eventHandlers.dispatch( - LogEvent.warn( - String.format( - warning, - platform.getArchitecture(), - platform.getOs(), - containerConfig.getArchitecture(), - containerConfig.getOs(), - baseImageName))); + String msg = + String.format( + "the configured platform (%s/%s) doesn't match the platform (%s/%s) of the base image (%s)", + platform.getArchitecture(), + platform.getOs(), + containerConfig.getArchitecture(), + containerConfig.getOs(), + baseImageName); + throw new PlatformNotFoundInBaseImageException(msg); } } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java index a9c6fa060c..6ce49ba41a 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java @@ -41,11 +41,12 @@ import com.google.cloud.tools.jib.image.json.ImageMetadataTemplate; import com.google.cloud.tools.jib.image.json.JsonToImageTranslator; import com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate; +import com.google.cloud.tools.jib.image.json.ManifestListTemplate; import com.google.cloud.tools.jib.image.json.ManifestTemplate; +import com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException; import com.google.cloud.tools.jib.image.json.UnknownManifestFormatException; import com.google.cloud.tools.jib.image.json.UnlistedPlatformInManifestListException; import com.google.cloud.tools.jib.image.json.V21ManifestTemplate; -import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate; import com.google.cloud.tools.jib.json.JsonTemplateMapper; import com.google.cloud.tools.jib.registry.ManifestAndDigest; import com.google.cloud.tools.jib.registry.RegistryClient; @@ -233,7 +234,7 @@ Optional tryMirrors( progressDispatcher1.newChildProducer().create("trying mirror " + mirror, 2)) { RegistryClient registryClient = buildContext.newBaseImageRegistryClientFactory(mirror).newRegistryClient(); - List images = pullPublicImages(eventHandlers, registryClient, progressDispatcher2); + List images = pullPublicImages(registryClient, progressDispatcher2); eventHandlers.dispatch(LogEvent.info("pulled manifest from mirror " + mirror)); return Optional.of(new ImagesAndRegistryClient(images, registryClient)); @@ -249,9 +250,7 @@ Optional tryMirrors( } private List pullPublicImages( - EventHandlers eventHandlers, - RegistryClient registryClient, - ProgressEventDispatcher progressDispatcher) + RegistryClient registryClient, ProgressEventDispatcher progressDispatcher) throws IOException, RegistryException, LayerCountMismatchException, BadContainerConfigurationFormatException { try { @@ -316,8 +315,7 @@ private List pullBaseImages( JsonToImageTranslator.toImage(imageManifest, containerConfig)); } - // TODO: support OciIndexTemplate once AbstractManifestPuller starts to accept it. - Verify.verify(manifestTemplate instanceof V22ManifestListTemplate); + Verify.verify(manifestTemplate instanceof ManifestListTemplate); List manifestsAndConfigs = new ArrayList<>(); ImmutableList.Builder images = ImmutableList.builder(); @@ -333,7 +331,7 @@ private List pullBaseImages( String manifestDigest = lookUpPlatformSpecificImageManifest( - (V22ManifestListTemplate) manifestTemplate, platform); + (ManifestListTemplate) manifestTemplate, platform); // TODO: pull multiple manifests (+ container configs) in parallel. ManifestAndDigest imageManifestAndDigest = registryClient.pullManifest(manifestDigest); progressDispatcher2.dispatchProgress(1); @@ -361,10 +359,9 @@ private List pullBaseImages( * Looks through a manifest list for the manifest matching the {@code platform} and returns the * digest of the first manifest it finds. */ - // TODO: support OciIndexTemplate once AbstractManifestPuller starts to accept it. @VisibleForTesting String lookUpPlatformSpecificImageManifest( - V22ManifestListTemplate manifestListTemplate, Platform platform) + ManifestListTemplate manifestListTemplate, Platform platform) throws UnlistedPlatformInManifestListException { EventHandlers eventHandlers = buildContext.getEventHandlers(); @@ -444,7 +441,8 @@ private ContainerConfigurationTemplate pullContainerConfigJson( @VisibleForTesting List getCachedBaseImages() throws IOException, CacheCorruptedException, BadContainerConfigurationFormatException, - LayerCountMismatchException, UnlistedPlatformInManifestListException { + LayerCountMismatchException, UnlistedPlatformInManifestListException, + PlatformNotFoundInBaseImageException { ImageReference baseImage = buildContext.getBaseImageConfiguration().getImage(); Optional metadata = buildContext.getBaseImageLayersCache().retrieveMetadata(baseImage); @@ -457,26 +455,19 @@ List getCachedBaseImages() if (manifestList == null) { Verify.verify(manifestsAndConfigs.size() == 1); - ManifestTemplate manifest = manifestsAndConfigs.get(0).getManifest(); - if (manifest instanceof V21ManifestTemplate) { - return Collections.singletonList( - JsonToImageTranslator.toImage((V21ManifestTemplate) manifest)); + ManifestAndConfigTemplate manifestAndConfig = manifestsAndConfigs.get(0); + Optional cachedImage = getBaseImageIfAllLayersCached(manifestAndConfig, true); + if (!cachedImage.isPresent()) { + return Collections.emptyList(); } - - ContainerConfigurationTemplate containerConfig = - Verify.verifyNotNull(manifestsAndConfigs.get(0).getConfig()); - PlatformChecker.checkManifestPlatform(buildContext, containerConfig); - - return Collections.singletonList( - JsonToImageTranslator.toImage( - (BuildableManifestTemplate) Verify.verifyNotNull(manifest), containerConfig)); + return Collections.singletonList(cachedImage.get()); } // Manifest list cached. Identify matching platforms and check if all of them are cached. ImmutableList.Builder images = ImmutableList.builder(); for (Platform platform : buildContext.getContainerConfiguration().getPlatforms()) { String manifestDigest = - lookUpPlatformSpecificImageManifest((V22ManifestListTemplate) manifestList, platform); + lookUpPlatformSpecificImageManifest((ManifestListTemplate) manifestList, platform); Optional manifestAndConfigFound = manifestsAndConfigs.stream() @@ -485,13 +476,52 @@ List getCachedBaseImages() if (!manifestAndConfigFound.isPresent()) { return Collections.emptyList(); } - - ManifestTemplate manifest = Verify.verifyNotNull(manifestAndConfigFound.get().getManifest()); - ContainerConfigurationTemplate containerConfig = - Verify.verifyNotNull(manifestAndConfigFound.get().getConfig()); - images.add( - JsonToImageTranslator.toImage((BuildableManifestTemplate) manifest, containerConfig)); + Optional cachedImage = + getBaseImageIfAllLayersCached(manifestAndConfigFound.get(), false); + if (!cachedImage.isPresent()) { + return Collections.emptyList(); + } + images.add(cachedImage.get()); } return images.build(); } + + /** + * Helper method to retrieve a base image from cache given manifest and container config. Does not + * return image if any layers of base image are missing in cache. + * + * @param manifestAndConfig stores an image manifest and a container config + * @param isSingleManifest true if base image is not a manifest list + * @return the single cached {@link Image} found, or {@code Optional#empty()} if the base image + * not found in cache with all layers present. + * @throws BadContainerConfigurationFormatException if the container configuration is in a bad + * format + * @throws PlatformNotFoundInBaseImageException if build target platform is not found in the base + * image + * @throws LayerCountMismatchException LayerCountMismatchException if the manifest and + * configuration contain conflicting layer information + */ + private Optional getBaseImageIfAllLayersCached( + ManifestAndConfigTemplate manifestAndConfig, boolean isSingleManifest) + throws BadContainerConfigurationFormatException, PlatformNotFoundInBaseImageException, + LayerCountMismatchException { + Cache baseImageLayersCache = buildContext.getBaseImageLayersCache(); + ManifestTemplate manifest = Verify.verifyNotNull(manifestAndConfig.getManifest()); + + // Verify all layers described in manifest are present in cache + if (!baseImageLayersCache.areAllLayersCached(manifest)) { + return Optional.empty(); + } + if (manifest instanceof V21ManifestTemplate) { + return Optional.of(JsonToImageTranslator.toImage((V21ManifestTemplate) manifest)); + } + ContainerConfigurationTemplate containerConfig = + Verify.verifyNotNull(manifestAndConfig.getConfig()); + if (isSingleManifest) { + // If base image is not a manifest list, check and warn misconfigured platforms. + PlatformChecker.checkManifestPlatform(buildContext, containerConfig); + } + return Optional.of( + JsonToImageTranslator.toImage((BuildableManifestTemplate) manifest, containerConfig)); + } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushImageStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushImageStep.java index ef3600c457..04861253f1 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushImageStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PushImageStep.java @@ -36,6 +36,7 @@ import java.util.Collections; import java.util.Set; import java.util.concurrent.Callable; +import java.util.stream.Collectors; /** * Pushes a manifest or a manifest list for a tag. If not a manifest list, returns the manifest @@ -55,31 +56,26 @@ static ImmutableList makeList( Image builtImage, boolean manifestAlreadyExists) throws IOException { - boolean singlePlatform = buildContext.getContainerConfiguration().getPlatforms().size() == 1; - Set tags = buildContext.getAllTargetImageTags(); - int numPushers = singlePlatform ? tags.size() : 1; + // Gets the image manifest to push. + BuildableManifestTemplate manifestTemplate = + new ImageToJsonTranslator(builtImage) + .getManifestTemplate( + buildContext.getTargetFormat(), containerConfigurationDigestAndSize); + DescriptorDigest manifestDigest = Digests.computeJsonDigest(manifestTemplate); + Set imageQualifiers = getImageQualifiers(buildContext, builtImage, manifestDigest); EventHandlers eventHandlers = buildContext.getEventHandlers(); try (TimerEventDispatcher ignored = new TimerEventDispatcher(eventHandlers, "Preparing manifest pushers"); ProgressEventDispatcher progressDispatcher = - progressEventDispatcherFactory.create("launching manifest pushers", numPushers)) { + progressEventDispatcherFactory.create( + "launching manifest pushers", imageQualifiers.size())) { if (JibSystemProperties.skipExistingImages() && manifestAlreadyExists) { eventHandlers.dispatch(LogEvent.info("Skipping pushing manifest; already exists.")); return ImmutableList.of(); } - // Gets the image manifest to push. - BuildableManifestTemplate manifestTemplate = - new ImageToJsonTranslator(builtImage) - .getManifestTemplate( - buildContext.getTargetFormat(), containerConfigurationDigestAndSize); - - DescriptorDigest manifestDigest = Digests.computeJsonDigest(manifestTemplate); - - Set imageQualifiers = - singlePlatform ? tags : Collections.singleton(manifestDigest.toString()); return imageQualifiers.stream() .map( qualifier -> @@ -95,6 +91,20 @@ static ImmutableList makeList( } } + private static Set getImageQualifiers( + BuildContext buildContext, Image builtImage, DescriptorDigest manifestDigest) { + boolean singlePlatform = buildContext.getContainerConfiguration().getPlatforms().size() == 1; + Set tags = buildContext.getAllTargetImageTags(); + if (singlePlatform) { + return tags; + } + if (buildContext.getEnablePlatformTags()) { + String architecture = builtImage.getArchitecture(); + return tags.stream().map(tag -> tag + "-" + architecture).collect(Collectors.toSet()); + } + return Collections.singleton(manifestDigest.toString()); + } + static ImmutableList makeListForManifestList( BuildContext buildContext, ProgressEventDispatcher.Factory progressEventDispatcherFactory, @@ -170,7 +180,7 @@ public BuildResult call() throws IOException, RegistryException { eventHandlers.dispatch(LogEvent.info("Pushing manifest for " + imageQualifier + "...")); registryClient.pushManifest(manifestTemplate, imageQualifier); - return new BuildResult(imageDigest, imageId); + return new BuildResult(imageDigest, imageId, true); } } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java index 41a0c37c2b..4cfeb5c173 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java @@ -17,13 +17,16 @@ package com.google.cloud.tools.jib.builder.steps; import com.google.cloud.tools.jib.api.DescriptorDigest; +import com.google.cloud.tools.jib.api.DockerClient; +import com.google.cloud.tools.jib.api.DockerInfoDetails; +import com.google.cloud.tools.jib.api.LogEvent; import com.google.cloud.tools.jib.blob.BlobDescriptor; import com.google.cloud.tools.jib.builder.ProgressEventDispatcher; import com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage; import com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient; import com.google.cloud.tools.jib.configuration.BuildContext; import com.google.cloud.tools.jib.configuration.ImageConfiguration; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.global.JibSystemProperties; import com.google.cloud.tools.jib.image.Image; @@ -413,7 +416,8 @@ private void buildAndCacheApplicationLayers( BuildAndCacheApplicationLayerStep.makeList(buildContext, progressDispatcherFactory)); } - private void buildImages(ProgressEventDispatcher.Factory progressDispatcherFactory) { + @VisibleForTesting + void buildImages(ProgressEventDispatcher.Factory progressDispatcherFactory) { results.baseImagesAndBuiltImages = executorService.submit( () -> { @@ -573,15 +577,23 @@ private Future pushImage( results.manifestCheckResult.get().isPresent())); realizeFutures(manifestPushResults); + return manifestPushResults.isEmpty() ? new BuildResult( results.manifestCheckResult.get().get().getDigest(), - Verify.verifyNotNull(containerConfigPushResult).get().getDigest()) + Verify.verifyNotNull(containerConfigPushResult).get().getDigest(), + isImagePushed(results.manifestCheckResult.get())) // Manifest pushers return the same BuildResult. : manifestPushResults.get(0).get(); }); } + @VisibleForTesting + boolean isImagePushed(Optional> manifestResult) { + + return !(JibSystemProperties.skipExistingImages() && manifestResult.isPresent()); + } + private void pushManifestList(ProgressEventDispatcher.Factory progressDispatcherFactory) { results.buildResult = executorService.submit( @@ -608,11 +620,12 @@ private void loadDocker( results.buildResult = executorService.submit( () -> { - Verify.verify( - results.baseImagesAndBuiltImages.get().size() == 1, - "multi-platform image building not supported when pushing to Docker engine"); + DockerInfoDetails dockerInfoDetails = dockerClient.info(); + String osType = dockerInfoDetails.getOsType(); + String architecture = normalizeArchitecture(dockerInfoDetails.getArchitecture()); Image builtImage = - results.baseImagesAndBuiltImages.get().values().iterator().next().get(); + fetchBuiltImageForLocalBuild( + osType, architecture, buildContext.getEventHandlers()); return new LoadDockerStep( buildContext, progressDispatcherFactory, dockerClient, builtImage) .call(); @@ -639,4 +652,37 @@ private void writeTarFile( private List> scheduleCallables(ImmutableList> callables) { return callables.stream().map(executorService::submit).collect(Collectors.toList()); } + + @VisibleForTesting + String normalizeArchitecture(String architecture) { + // Create mapping based on https://docs.docker.com/engine/install/#supported-platforms + if (architecture.equals("x86_64")) { + return "amd64"; + } else if (architecture.equals("aarch64")) { + return "arm64"; + } + return architecture; + } + + @VisibleForTesting + Image fetchBuiltImageForLocalBuild( + String osType, String architecture, EventHandlers eventHandlers) + throws InterruptedException, ExecutionException { + if (results.baseImagesAndBuiltImages.get().size() > 1) { + eventHandlers.dispatch( + LogEvent.warn( + String.format( + "Detected multi-platform configuration, only building image that matches the local Docker Engine's os and architecture (%s/%s) or " + + "the first platform specified", + osType, architecture))); + for (Map.Entry> imageEntry : + results.baseImagesAndBuiltImages.get().entrySet()) { + Image image = imageEntry.getValue().get(); + if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) { + return image; + } + } + } + return results.baseImagesAndBuiltImages.get().values().iterator().next().get(); + } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/Cache.java b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/Cache.java index 29c814e840..e46912ecbc 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/Cache.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/Cache.java @@ -25,6 +25,7 @@ import com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate; import com.google.cloud.tools.jib.image.json.ImageMetadataTemplate; import com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate; +import com.google.cloud.tools.jib.image.json.ManifestTemplate; import com.google.cloud.tools.jib.image.json.V21ManifestTemplate; import com.google.common.collect.ImmutableList; import java.io.IOException; @@ -192,6 +193,16 @@ public Optional retrieveMetadata(ImageReference imageRefe return cacheStorageReader.retrieveMetadata(imageReference); } + /** + * Returns {@code true} if all image layers described in a manifest exist in the cache. + * + * @param manifest the image manifest + * @return a boolean + */ + public boolean areAllLayersCached(ManifestTemplate manifest) { + return cacheStorageReader.areAllLayersCached(manifest); + } + /** * Retrieves the {@link CachedLayer} that was built from the {@code layerEntries}. * diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageReader.java b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageReader.java index f5c9508e71..0cce36506b 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageReader.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageReader.java @@ -81,6 +81,38 @@ static void verifyImageMetadata(ImageMetadataTemplate metadata, Path metadataCac this.cacheStorageFiles = cacheStorageFiles; } + /** + * Returns {@code true} if all image layers described in a manifest have a corresponding file + * entry in the cache. + * + * @param manifest the image manifest + * @return a boolean + */ + boolean areAllLayersCached(ManifestTemplate manifest) { + + List layerDigests; + + if (manifest instanceof V21ManifestTemplate) { + layerDigests = ((V21ManifestTemplate) manifest).getLayerDigests(); + } else if (manifest instanceof BuildableManifestTemplate) { + layerDigests = + ((BuildableManifestTemplate) manifest) + .getLayers().stream() + .map(BuildableManifestTemplate.ContentDescriptorTemplate::getDigest) + .collect(Collectors.toList()); + } else { + throw new IllegalArgumentException("Unknown manifest type: " + manifest); + } + + for (DescriptorDigest layerDigest : layerDigests) { + Path layerDirectory = cacheStorageFiles.getLayerDirectory(layerDigest); + if (!Files.exists(layerDirectory)) { + return false; + } + } + return true; + } + /** * Retrieves the cached image metadata (a manifest list and a list of manifest/container * configuration pairs) for an image reference. diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageWriter.java b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageWriter.java index 56e385cc0d..4f059fba5c 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageWriter.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/cache/CacheStorageWriter.java @@ -50,9 +50,10 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; -import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.annotation.Nullable; +import org.apache.commons.compress.compressors.CompressorException; +import org.apache.commons.compress.compressors.CompressorStreamFactory; /** Writes to the default cache storage engine. */ class CacheStorageWriter { @@ -159,9 +160,13 @@ static void moveIfDoesNotExist(Path source, Path destination) throws IOException */ private static DescriptorDigest getDiffIdByDecompressingFile(Path compressedFile) throws IOException { - try (InputStream fileInputStream = - new BufferedInputStream(new GZIPInputStream(Files.newInputStream(compressedFile)))) { - return Digests.computeDigest(fileInputStream).getDigest(); + try (InputStream in = + new CompressorStreamFactory(true) + .createCompressorInputStream( + new BufferedInputStream(Files.newInputStream(compressedFile)))) { + return Digests.computeDigest(in).getDigest(); + } catch (CompressorException e) { + throw new IOException(e); } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/BuildContext.java b/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/BuildContext.java index caa5780008..31f29eb3c1 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/BuildContext.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/BuildContext.java @@ -83,6 +83,7 @@ public static class Builder { @Nullable private ExecutorService executorService; private boolean alwaysCacheBaseImage = false; private ImmutableListMultimap registryMirrors = ImmutableListMultimap.of(); + private boolean enablePlatformTags = false; private Builder() {} @@ -120,6 +121,19 @@ public Builder setAdditionalTargetImageTags(Set tags) { return this; } + /** + * Sets whether to automatically add architecture suffix to tags for platform-specific images + * when building multi-platform images. For example, when building amd64 and arm64 images for a + * given tag, the final tags will be {@code -amd64} and {@code -arm64}. + * + * @param enablePlatformTags whether to append architecture suffix to tags + * @return this + */ + public Builder setEnablePlatformTags(boolean enablePlatformTags) { + this.enablePlatformTags = enablePlatformTags; + return this; + } + /** * Sets configuration parameters for the container. * @@ -328,7 +342,8 @@ public BuildContext build() throws CacheDirectoryCreationException { executorService == null ? Executors.newCachedThreadPool() : executorService, executorService == null, // shutDownExecutorService alwaysCacheBaseImage, - registryMirrors); + registryMirrors, + enablePlatformTags); case 1: throw new IllegalStateException(missingFields.get(0) + " is required but not set"); @@ -386,6 +401,7 @@ public static Builder builder() { private final boolean shutDownExecutorService; private final boolean alwaysCacheBaseImage; private final ImmutableListMultimap registryMirrors; + private final boolean enablePlatformTags; /** Instantiate with {@link #builder}. */ private BuildContext( @@ -405,7 +421,8 @@ private BuildContext( ExecutorService executorService, boolean shutDownExecutorService, boolean alwaysCacheBaseImage, - ImmutableListMultimap registryMirrors) { + ImmutableListMultimap registryMirrors, + boolean enablePlatformTags) { this.baseImageConfiguration = baseImageConfiguration; this.targetImageConfiguration = targetImageConfiguration; this.additionalTargetImageTags = additionalTargetImageTags; @@ -423,12 +440,17 @@ private BuildContext( this.shutDownExecutorService = shutDownExecutorService; this.alwaysCacheBaseImage = alwaysCacheBaseImage; this.registryMirrors = registryMirrors; + this.enablePlatformTags = enablePlatformTags; } public ImageConfiguration getBaseImageConfiguration() { return baseImageConfiguration; } + public boolean getEnablePlatformTags() { + return enablePlatformTags; + } + public ImageConfiguration getTargetImageConfiguration() { return targetImageConfiguration; } @@ -526,22 +548,23 @@ public ImmutableListMultimap getRegistryMirrors() { } /** - * Creates a new {@link RegistryClient.Factory} for the base image with fields from the build - * configuration. The server URL is derived from the base {@link ImageConfiguration}. + * Creates a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} for the base + * image with fields from the build configuration. The server URL is derived from the base {@link + * ImageConfiguration}. * - * @return a new {@link RegistryClient.Factory} + * @return a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} */ public RegistryClient.Factory newBaseImageRegistryClientFactory() { return newBaseImageRegistryClientFactory(baseImageConfiguration.getImageRegistry()); } /** - * Creates a new {@link RegistryClient.Factory} for the base image repository on the registry - * {@code serverUrl}. Compared to @link #newBaseImageRegistryClientFactory()), this method is - * useful to try a mirror. + * Creates a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} for the base + * image repository on the registry {@code serverUrl}. Compared to @link + * #newBaseImageRegistryClientFactory()), this method is useful to try a mirror. * * @param serverUrl the server URL for the registry (for example, {@code gcr.io}) - * @return a new {@link RegistryClient.Factory} + * @return a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} */ public RegistryClient.Factory newBaseImageRegistryClientFactory(String serverUrl) { return RegistryClient.factory( @@ -550,10 +573,10 @@ public RegistryClient.Factory newBaseImageRegistryClientFactory(String serverUrl } /** - * Creates a new {@link RegistryClient.Factory} for the target image with fields from the build - * configuration. + * Creates a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} for the target + * image with fields from the build configuration. * - * @return a new {@link RegistryClient.Factory} + * @return a new {@link com.google.cloud.tools.jib.registry.RegistryClient.Factory} */ public RegistryClient.Factory newTargetImageRegistryClientFactory() { // if base and target are on the same registry, try enabling cross-repository mounts diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ContainerConfiguration.java b/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ContainerConfiguration.java index 6656917386..f152705b7d 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ContainerConfiguration.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ContainerConfiguration.java @@ -34,6 +34,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nullable; /** Immutable configuration options for the container. */ @@ -141,9 +142,14 @@ public Builder setEnvironment(@Nullable Map environmentMap) { Preconditions.checkArgument( !Iterables.any(environmentMap.keySet(), Objects::isNull), "environment map contains null keys"); + String nullValuedKeys = + environmentMap.entrySet().stream() + .filter(entry -> entry.getValue() == null) + .map(Map.Entry::getKey) + .collect(Collectors.joining(", ")); Preconditions.checkArgument( - !Iterables.any(environmentMap.values(), Objects::isNull), - "environment map contains null values"); + nullValuedKeys.isEmpty(), + "environment map contains null values for key(s): " + nullValuedKeys); this.environmentMap = new HashMap<>(environmentMap); } return this; diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ImageConfiguration.java b/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ImageConfiguration.java index d561401325..91e8818713 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ImageConfiguration.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/configuration/ImageConfiguration.java @@ -17,8 +17,8 @@ package com.google.cloud.tools.jib.configuration; import com.google.cloud.tools.jib.api.CredentialRetriever; +import com.google.cloud.tools.jib.api.DockerClient; import com.google.cloud.tools.jib.api.ImageReference; -import com.google.cloud.tools.jib.docker.DockerClient; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.nio.file.Path; diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java similarity index 77% rename from jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClient.java rename to jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java index ac17fc8935..035c9316f3 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClient.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java @@ -19,6 +19,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.cloud.tools.jib.api.DescriptorDigest; +import com.google.cloud.tools.jib.api.DockerClient; +import com.google.cloud.tools.jib.api.DockerInfoDetails; +import com.google.cloud.tools.jib.api.ImageDetails; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.http.NotifyingOutputStream; import com.google.cloud.tools.jib.image.ImageTarball; @@ -44,17 +47,23 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Function; /** Calls out to the {@code docker} CLI. */ -public class DockerClient { +public class CliDockerClient implements DockerClient { /** * Contains the size, image ID, and diff IDs of an image inspected with {@code docker inspect}. */ @JsonIgnoreProperties(ignoreUnknown = true) - public static class DockerImageDetails implements JsonTemplate { + public static class DockerImageDetails implements JsonTemplate, ImageDetails { @JsonIgnoreProperties(ignoreUnknown = true) private static class RootFsTemplate implements JsonTemplate { @@ -71,10 +80,12 @@ private static class RootFsTemplate implements JsonTemplate { @JsonProperty("RootFS") private final RootFsTemplate rootFs = new RootFsTemplate(); + @Override public long getSize() { return size; } + @Override public DescriptorDigest getImageId() throws DigestException { return DescriptorDigest.fromDigest(imageId); } @@ -85,6 +96,7 @@ public DescriptorDigest getImageId() throws DigestException { * @return a list of diff ids * @throws DigestException if a digest is invalid */ + @Override public List getDiffIds() throws DigestException { List processedDiffIds = new ArrayList<>(rootFs.layers.size()); for (String diffId : rootFs.layers) { @@ -98,30 +110,34 @@ public List getDiffIds() throws DigestException { public static final Path DEFAULT_DOCKER_CLIENT = Paths.get("docker"); /** - * Checks if Docker is installed on the user's system and accessible by running the default {@code - * docker} command. + * 10 minute timeout to ensure that Jib doesn't get stuck indefinitely when expecting a docker + * output. + */ + public static final Long DOCKER_OUTPUT_TIMEOUT = (long) 10 * 60 * 1000; + + /** + * Checks if Docker is installed on the user's system by running the `docker` command. * * @return {@code true} if Docker is installed on the user's system and accessible */ public static boolean isDefaultDockerInstalled() { - return isDockerInstalled(DEFAULT_DOCKER_CLIENT); + try { + new ProcessBuilder(DEFAULT_DOCKER_CLIENT.toString()).start(); + return true; + } catch (IOException ex) { + return false; + } } /** - * Checks if Docker is installed on the user's system and accessible by running the given {@code - * docker} executable. + * Checks if Docker is installed on the user's system and by verifying if the executable path + * provided has the appropriate permissions. * * @param dockerExecutable path to the executable to test running * @return {@code true} if Docker is installed on the user's system and accessible */ public static boolean isDockerInstalled(Path dockerExecutable) { - try { - new ProcessBuilder(dockerExecutable.toString()).start(); - return true; - - } catch (IOException ex) { - return false; - } + return Files.exists(dockerExecutable); } /** @@ -165,28 +181,41 @@ private static String getStderrOutput(Process process) { * @param dockerExecutable path to {@code docker} * @param dockerEnvironment environment variables for {@code docker} */ - public DockerClient(Path dockerExecutable, Map dockerEnvironment) { + public CliDockerClient(Path dockerExecutable, Map dockerEnvironment) { this( defaultProcessBuilderFactory( dockerExecutable.toString(), ImmutableMap.copyOf(dockerEnvironment))); } @VisibleForTesting - DockerClient(Function, ProcessBuilder> processBuilderFactory) { + CliDockerClient(Function, ProcessBuilder> processBuilderFactory) { this.processBuilderFactory = processBuilderFactory; } - /** - * Loads an image tarball into the Docker daemon. - * - * @see https://docs.docker.com/engine/reference/commandline/load - * @param imageTarball the built container tarball - * @param writtenByteCountListener callback to call when bytes are loaded - * @return stdout from {@code docker} - * @throws InterruptedException if the 'docker load' process is interrupted - * @throws IOException if streaming the blob to 'docker load' fails - */ + @Override + public boolean supported(Map parameters) { + return true; + } + + @Override + public DockerInfoDetails info() throws IOException, InterruptedException { + // Runs 'docker info'. + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future readerFuture = executor.submit(this::fetchInfoDetails); + try { + DockerInfoDetails details = readerFuture.get(DOCKER_OUTPUT_TIMEOUT, TimeUnit.MILLISECONDS); + return details; + } catch (TimeoutException e) { + readerFuture.cancel(true); // Interrupt the reader thread + throw new IOException("Timeout reached while waiting for 'docker info' output"); + } catch (ExecutionException e) { + throw new IOException("Failed to read output of 'docker info': " + e.getMessage()); + } finally { + executor.shutdownNow(); + } + } + + @Override public String load(ImageTarball imageTarball, Consumer writtenByteCountListener) throws InterruptedException, IOException { // Runs 'docker load'. @@ -224,17 +253,7 @@ public String load(ImageTarball imageTarball, Consumer writtenByteCountLis } } - /** - * Saves an image tarball from the Docker daemon. - * - * @see https://docs.docker.com/engine/reference/commandline/save - * @param imageReference the image to save - * @param outputPath the destination path to save the output tarball - * @param writtenByteCountListener callback to call when bytes are saved - * @throws InterruptedException if the 'docker save' process is interrupted - * @throws IOException if creating the tarball fails - */ + @Override public void save( ImageReference imageReference, Path outputPath, Consumer writtenByteCountListener) throws InterruptedException, IOException { @@ -253,14 +272,7 @@ public void save( } } - /** - * Gets the size, image ID, and diff IDs of an image in the Docker daemon. - * - * @param imageReference the image to inspect - * @return the size, image ID, and diff IDs of the image - * @throws IOException if an I/O exception occurs or {@code docker inspect} failed - * @throws InterruptedException if the {@code docker inspect} process was interrupted - */ + @Override public DockerImageDetails inspect(ImageReference imageReference) throws IOException, InterruptedException { Process inspectProcess = @@ -276,4 +288,14 @@ public DockerImageDetails inspect(ImageReference imageReference) private Process docker(String... subCommand) throws IOException { return processBuilderFactory.apply(Arrays.asList(subCommand)).start(); } + + private DockerInfoDetails fetchInfoDetails() throws IOException, InterruptedException { + Process infoProcess = docker("info", "-f", "{{json .}}"); + InputStream inputStream = infoProcess.getInputStream(); + if (infoProcess.waitFor() != 0) { + throw new IOException( + "'docker info' command failed with error: " + getStderrOutput(infoProcess)); + } + return JsonTemplateMapper.readJson(inputStream, DockerInfoDetails.class); + } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClientResolver.java b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClientResolver.java new file mode 100644 index 0000000000..025114013c --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/DockerClientResolver.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.docker; + +import com.google.cloud.tools.jib.api.DockerClient; +import java.util.Map; +import java.util.Optional; +import java.util.ServiceLoader; + +public class DockerClientResolver { + + private DockerClientResolver() {} + + /** + * Look for supported DockerClient. + * + * @param parameters needed by the dockerClient + * @return dockerClient if any is found + */ + public static Optional resolve(Map parameters) { + ServiceLoader dockerClients = ServiceLoader.load(DockerClient.class); + for (DockerClient dockerClient : dockerClients) { + if (dockerClient.supported(parameters)) { + return Optional.of(dockerClient); + } + } + return Optional.empty(); + } +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplate.java index 2d5fd641c0..5048f33c4e 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplate.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplate.java @@ -16,6 +16,7 @@ package com.google.cloud.tools.jib.docker.json; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.cloud.tools.jib.json.JsonTemplate; import com.google.common.annotations.VisibleForTesting; @@ -27,7 +28,7 @@ * a tag is missing, it explicitly should use "latest". * *

Note that this is a template for a single Manifest entry, while the entire Docker Manifest - * should be {@code List}. + * should be {@code List}. * *

Example manifest entry JSON: * @@ -46,6 +47,7 @@ * @see Docker load * source */ +@JsonIgnoreProperties(ignoreUnknown = true) public class DockerManifestEntryTemplate implements JsonTemplate { @JsonProperty("Config") diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.java b/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.java index 21270850b6..0d4c97b385 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactory.java @@ -45,7 +45,8 @@ public class CredentialRetrieverFactory { @VisibleForTesting @FunctionalInterface interface DockerCredentialHelperFactory { - DockerCredentialHelper create(String registry, Path credentialHelper); + DockerCredentialHelper create( + String registry, Path credentialHelper, Map environment); } /** Used for passing in mock {@link GoogleCredentials} for testing. */ @@ -81,24 +82,46 @@ public static CredentialRetrieverFactory forImage( imageReference, logger, DockerCredentialHelper::new, - GoogleCredentials::getApplicationDefault); + GoogleCredentials::getApplicationDefault, + Collections.emptyMap()); + } + + /** + * Creates a new {@link CredentialRetrieverFactory} for an image. + * + * @param imageReference the image the credential are for + * @param logger a consumer for handling log events + * @param environment environment variables for credential helper + * @return a new {@link CredentialRetrieverFactory} + */ + public static CredentialRetrieverFactory forImage( + ImageReference imageReference, Consumer logger, Map environment) { + return new CredentialRetrieverFactory( + imageReference, + logger, + DockerCredentialHelper::new, + GoogleCredentials::getApplicationDefault, + environment); } private final ImageReference imageReference; private final Consumer logger; private final DockerCredentialHelperFactory dockerCredentialHelperFactory; private final GoogleCredentialsProvider googleCredentialsProvider; + private final Map environment; @VisibleForTesting CredentialRetrieverFactory( ImageReference imageReference, Consumer logger, DockerCredentialHelperFactory dockerCredentialHelperFactory, - GoogleCredentialsProvider googleCredentialsProvider) { + GoogleCredentialsProvider googleCredentialsProvider, + Map environment) { this.imageReference = imageReference; this.logger = logger; this.dockerCredentialHelperFactory = dockerCredentialHelperFactory; this.googleCredentialsProvider = googleCredentialsProvider; + this.environment = environment; } /** @@ -293,7 +316,7 @@ private Credential retrieveFromDockerCredentialHelper(Path credentialHelper) IOException { Credential credentials = dockerCredentialHelperFactory - .create(imageReference.getRegistry(), credentialHelper) + .create(imageReference.getRegistry(), credentialHelper, environment) .retrieve(); logGotCredentialsFrom("credential helper " + credentialHelper.getFileName().toString()); return credentials; diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/http/Authorization.java b/jib-core/src/main/java/com/google/cloud/tools/jib/http/Authorization.java index e4dd284cbd..66d7899b1f 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/http/Authorization.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/http/Authorization.java @@ -16,8 +16,8 @@ package com.google.cloud.tools.jib.http; -import com.google.api.client.util.Base64; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Objects; /** @@ -38,7 +38,7 @@ public class Authorization { */ public static Authorization fromBasicCredentials(String username, String secret) { String credentials = username + ":" + secret; - String token = Base64.encodeBase64String(credentials.getBytes(StandardCharsets.UTF_8)); + String token = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); return new Authorization("Basic", token); } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java index c258eb2b5f..ea7ea5a943 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/ReproducibleLayerBuilder.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; @@ -76,11 +77,11 @@ private void add(TarArchiveEntry tarArchiveEntry) throws IOException { if (namePath.getParent() != namePath.getRoot()) { Path tarArchiveParentDir = Verify.verifyNotNull(namePath.getParent()); TarArchiveEntry dir = new TarArchiveEntry(DIRECTORY_FILE, tarArchiveParentDir.toString()); - dir.setModTime(FileEntriesLayer.DEFAULT_MODIFICATION_TIME.toEpochMilli()); dir.setUserId(0); dir.setGroupId(0); dir.setUserName(""); dir.setGroupName(""); + clearTimeHeaders(dir, FileEntriesLayer.DEFAULT_MODIFICATION_TIME); add(dir); } @@ -95,6 +96,20 @@ private List getSortedEntries() { } } + private static void clearTimeHeaders(TarArchiveEntry entry, Instant modTime) { + entry.setModTime(modTime.toEpochMilli()); + + String headerTime = Long.toString(modTime.getEpochSecond()); + final long nanos = modTime.getNano(); + if (nanos > 0) { + headerTime += "." + nanos; + } + entry.addPaxHeader("mtime", headerTime); + entry.addPaxHeader("atime", headerTime); + entry.addPaxHeader("ctime", headerTime); + entry.addPaxHeader("LIBARCHIVE.creationtime", headerTime); + } + private static void setUserAndGroup(TarArchiveEntry entry, FileEntry layerEntry) { entry.setUserId(0); entry.setGroupId(0); @@ -156,8 +171,8 @@ public Blob build() throws IOException { // Sets the entry's permissions by masking out the permission bits from the entry's mode (the // lowest 9 bits) then using a bitwise OR to set them to the layerEntry's permissions. entry.setMode((entry.getMode() & ~0777) | layerEntry.getPermissions().getPermissionBits()); - entry.setModTime(layerEntry.getModificationTime().toEpochMilli()); setUserAndGroup(entry, layerEntry); + clearTimeHeaders(entry, layerEntry.getModificationTime()); uniqueTarArchiveEntries.add(entry); } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/BuildableManifestTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/BuildableManifestTemplate.java index 967d59e50a..c61ff881df 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/BuildableManifestTemplate.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/BuildableManifestTemplate.java @@ -59,7 +59,7 @@ class ContentDescriptorTemplate implements JsonTemplate { /** Necessary for Jackson to create from JSON. */ @SuppressWarnings("unused") - private ContentDescriptorTemplate() {} + protected ContentDescriptorTemplate() {} public long getSize() { return size; diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListTemplate.java new file mode 100644 index 0000000000..8ed4cfa520 --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListTemplate.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.image.json; + +import java.util.List; + +/** + * Parent class for manifest lists. + * + * @see V22ManifestListTemplate Docker V2.2 format + * @see OciIndexTemplate OCI format + */ +public interface ManifestListTemplate extends ManifestTemplate { + + /** + * Returns a list of digests for a specific platform found in the manifest list. see + * https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list + * + * @param architecture the architecture of the target platform + * @param os the os of the target platform + * @return a list of matching digests + */ + List getDigestsForPlatform(String architecture, String os); +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/OciIndexTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/OciIndexTemplate.java index 6166b22609..6eeffaf4da 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/OciIndexTemplate.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/OciIndexTemplate.java @@ -16,11 +16,17 @@ package com.google.cloud.tools.jib.image.json; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.blob.BlobDescriptor; +import com.google.cloud.tools.jib.json.JsonTemplate; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nullable; /** * JSON template for OCI archive "index.json" file. @@ -36,6 +42,10 @@ * "mediaType": "application/vnd.oci.image.manifest.v1+json", * "digest": "sha256:e684b1dceef404268f17d4adf7f755fd9912b8ae64864b3954a83ebb8aa628b3", * "size": 1132, + * "platform": { + * "architecture": "ppc64le", + * "os": "linux" + * }, * "annotations": { * "org.opencontainers.image.ref.name": "gcr.io/project/image:tag" * } @@ -47,7 +57,7 @@ * @see OCI Image * Index Specification */ -public class OciIndexTemplate implements ManifestTemplate { +public class OciIndexTemplate implements ManifestListTemplate { /** The OCI Index media type. */ public static final String MEDIA_TYPE = "application/vnd.oci.image.index.v1+json"; @@ -55,8 +65,7 @@ public class OciIndexTemplate implements ManifestTemplate { private final int schemaVersion = 2; private final String mediaType = MEDIA_TYPE; - private final List manifests = - new ArrayList<>(); + private final List manifests = new ArrayList<>(); @Override public int getSchemaVersion() { @@ -75,16 +84,93 @@ public String getManifestMediaType() { * @param imageReferenceName the image reference name */ public void addManifest(BlobDescriptor descriptor, String imageReferenceName) { - BuildableManifestTemplate.ContentDescriptorTemplate contentDescriptorTemplate = - new BuildableManifestTemplate.ContentDescriptorTemplate( + ManifestDescriptorTemplate contentDescriptorTemplate = + new ManifestDescriptorTemplate( OciManifestTemplate.MANIFEST_MEDIA_TYPE, descriptor.getSize(), descriptor.getDigest()); contentDescriptorTemplate.setAnnotations( ImmutableMap.of("org.opencontainers.image.ref.name", imageReferenceName)); manifests.add(contentDescriptorTemplate); } + /** + * Adds a manifest. + * + * @param manifest a manifest descriptor + */ + public void addManifest(OciIndexTemplate.ManifestDescriptorTemplate manifest) { + manifests.add(manifest); + } + @VisibleForTesting - public List getManifests() { + public List getManifests() { return manifests; } + + @Override + public List getDigestsForPlatform(String architecture, String os) { + return getManifests().stream() + .filter( + manifest -> + manifest.platform != null + && os.equals(manifest.platform.os) + && architecture.equals(manifest.platform.architecture)) + .map(ManifestDescriptorTemplate::getDigest) + .filter(Objects::nonNull) + .map(DescriptorDigest::toString) + .collect(Collectors.toList()); + } + + /** + * Template for inner JSON object representing a single platform specific manifest. See OCI Image Index + * Specification + */ + public static class ManifestDescriptorTemplate + extends BuildableManifestTemplate.ContentDescriptorTemplate { + + ManifestDescriptorTemplate(String mediaType, long size, DescriptorDigest digest) { + super(mediaType, size, digest); + } + + /** Necessary for Jackson to create from JSON. */ + @SuppressWarnings("unused") + private ManifestDescriptorTemplate() { + super(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Platform implements JsonTemplate { + @Nullable private String architecture; + @Nullable private String os; + + @Nullable + public String getArchitecture() { + return architecture; + } + + @Nullable + public String getOs() { + return os; + } + } + + @Nullable private OciIndexTemplate.ManifestDescriptorTemplate.Platform platform; + + /** + * Sets a platform. + * + * @param architecture the manifest architecture + * @param os the manifest os + */ + public void setPlatform(String architecture, String os) { + platform = new OciIndexTemplate.ManifestDescriptorTemplate.Platform(); + platform.architecture = architecture; + platform.os = os; + } + + @Nullable + public OciIndexTemplate.ManifestDescriptorTemplate.Platform getPlatform() { + return platform; + } + } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/PlatformNotFoundInBaseImageException.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/PlatformNotFoundInBaseImageException.java new file mode 100644 index 0000000000..bafaac0a8b --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/PlatformNotFoundInBaseImageException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.image.json; + +import com.google.cloud.tools.jib.api.RegistryException; + +/** Exception thrown when build target platforms are not found in the base image. */ +public class PlatformNotFoundInBaseImageException extends RegistryException { + + public PlatformNotFoundInBaseImageException(String message) { + super(message); + } +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V22ManifestListTemplate.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V22ManifestListTemplate.java index c56e6c8859..3293587f91 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V22ManifestListTemplate.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/V22ManifestListTemplate.java @@ -63,7 +63,7 @@ * @see Image Manifest * Version 2, Schema 2: Manifest List */ -public class V22ManifestListTemplate implements ManifestTemplate { +public class V22ManifestListTemplate implements ManifestListTemplate { public static final String MANIFEST_MEDIA_TYPE = "application/vnd.docker.distribution.manifest.list.v2+json"; @@ -101,14 +101,7 @@ public List getManifests() { return Preconditions.checkNotNull(manifests); } - /** - * Returns a list of digests for a specific platform found in the manifest list. see - * https://docs.docker.com/registry/spec/manifest-v2-2/#manifest-list - * - * @param architecture the architecture of the target platform - * @param os the os of the target platform - * @return a list of matching digests - */ + @Override public List getDigestsForPlatform(String architecture, String os) { return getManifests().stream() .filter( diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AbstractManifestPuller.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AbstractManifestPuller.java index 1bb7522580..1845b63bbf 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AbstractManifestPuller.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AbstractManifestPuller.java @@ -80,12 +80,16 @@ public List getAccept() { if (manifestTemplateClass.equals(V22ManifestListTemplate.class)) { return Collections.singletonList(V22ManifestListTemplate.MANIFEST_MEDIA_TYPE); } + if (manifestTemplateClass.equals(OciIndexTemplate.class)) { + return Collections.singletonList(OciIndexTemplate.MEDIA_TYPE); + } return Arrays.asList( OciManifestTemplate.MANIFEST_MEDIA_TYPE, V22ManifestTemplate.MANIFEST_MEDIA_TYPE, V21ManifestTemplate.MEDIA_TYPE, - V22ManifestListTemplate.MANIFEST_MEDIA_TYPE); + V22ManifestListTemplate.MANIFEST_MEDIA_TYPE, + OciIndexTemplate.MEDIA_TYPE); } /** Parses the response body into a {@link ManifestAndDigest}. */ @@ -174,6 +178,10 @@ private T getManifestTemplateFromJson(String jsonString) return manifestTemplateClass.cast( JsonTemplateMapper.readJson(jsonString, V22ManifestListTemplate.class)); } + if (OciIndexTemplate.MEDIA_TYPE.equals(mediaType)) { + return manifestTemplateClass.cast( + JsonTemplateMapper.readJson(jsonString, OciIndexTemplate.class)); + } throw new UnknownManifestFormatException("Unknown mediaType: " + mediaType); } throw new UnknownManifestFormatException( diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java index 1b4acde65d..0fc0a219da 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java @@ -72,9 +72,9 @@ static Optional fromAuthenticationMethod( @Nullable String userAgent, FailoverHttpClient httpClient) throws RegistryAuthenticationFailedException { - // If the authentication method starts with 'basic ' (case insensitive), no registry + // If the authentication method starts with 'basic' (case insensitive), no registry // authentication is needed. - if (authenticationMethod.matches("^(?i)(basic) .*")) { + if (authenticationMethod.matches("^(?i)(basic).*")) { return Optional.empty(); } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java index 1ebed78104..a2d97e35bb 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.api.client.http.HttpStatusCodes; -import com.google.api.client.util.Base64; import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.api.LogEvent; @@ -44,6 +43,7 @@ import com.google.common.collect.Multimap; import java.io.IOException; import java.net.URL; +import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; @@ -195,10 +195,10 @@ static Multimap decodeTokenRepositoryGrants(String token) { // parts (header, payload, signature), collated with a ".". The header and payload are // JSON objects. String[] jwtParts = token.split("\\.", -1); - byte[] payloadData; - if (jwtParts.length != 3 || (payloadData = Base64.decodeBase64(jwtParts[1])) == null) { + if (jwtParts.length != 3) { return null; } + byte[] payloadData = Base64.getDecoder().decode(jwtParts[1]); // The payload looks like: // { diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java index 08bfd59139..2aa5ec3f32 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigCredentialRetriever.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.api.client.util.Base64; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.api.LogEvent; import com.google.cloud.tools.jib.registry.RegistryAliasGroup; @@ -31,6 +31,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Base64; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -88,7 +89,9 @@ public Optional retrieve(Consumer logger) throws IOExcepti } ObjectMapper objectMapper = - new ObjectMapper().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + JsonMapper.builder() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .build(); try (InputStream fileIn = Files.newInputStream(dockerConfigFile)) { if (legacyConfigFormat) { // legacy config format is the value of the "auths":{ } block of the new config (i.e., @@ -144,7 +147,7 @@ Optional retrieve(DockerConfig dockerConfig, Consumer logg if (auth.getAuth() != null) { // 'auth' is a basic authentication token that should be parsed back into credentials String usernameColonPassword = - new String(Base64.decodeBase64(auth.getAuth()), StandardCharsets.UTF_8); + new String(Base64.getDecoder().decode(auth.getAuth()), StandardCharsets.UTF_8); String username = usernameColonPassword.substring(0, usernameColonPassword.indexOf(":")); String password = usernameColonPassword.substring(usernameColonPassword.indexOf(":") + 1); logger.accept( diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelper.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelper.java index 3acbee8d8d..e7cd50f8e2 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelper.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelper.java @@ -31,8 +31,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.function.Function; import javax.annotation.Nullable; @@ -48,7 +50,8 @@ public class DockerCredentialHelper { private final String serverUrl; private final Path credentialHelper; private final Properties systemProperties; - private Function, ProcessBuilder> processBuilderFactory; + private final Function, ProcessBuilder> processBuilderFactory; + private final Map environment; /** Template for a Docker credential helper output. */ @VisibleForTesting @@ -73,7 +76,24 @@ static class DockerCredentialsTemplate implements JsonTemplate { * @param credentialHelper the path to the credential helper executable */ public DockerCredentialHelper(String serverUrl, Path credentialHelper) { - this(serverUrl, credentialHelper, System.getProperties(), ProcessBuilder::new); + this( + serverUrl, + credentialHelper, + System.getProperties(), + ProcessBuilder::new, + Collections.emptyMap()); + } + + /** + * Constructs a new {@link DockerCredentialHelper}. + * + * @param serverUrl the server URL to pass into the credential helper + * @param credentialHelper the path to the credential helper executable + * @param environment environment variables used in configuring the credential helper + */ + public DockerCredentialHelper( + String serverUrl, Path credentialHelper, Map environment) { + this(serverUrl, credentialHelper, System.getProperties(), ProcessBuilder::new, environment); } @VisibleForTesting @@ -81,11 +101,13 @@ public DockerCredentialHelper(String serverUrl, Path credentialHelper) { String serverUrl, Path credentialHelper, Properties systemProperties, - Function, ProcessBuilder> processBuilderFactory) { + Function, ProcessBuilder> processBuilderFactory, + Map environment) { this.serverUrl = serverUrl; this.credentialHelper = credentialHelper; this.systemProperties = systemProperties; this.processBuilderFactory = processBuilderFactory; + this.environment = environment; } /** @@ -131,7 +153,9 @@ private Credential retrieve(List credentialHelperCommand) throws IOException, CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException { try { - Process process = processBuilderFactory.apply(credentialHelperCommand).start(); + ProcessBuilder processBuilder = processBuilderFactory.apply(credentialHelperCommand); + processBuilder.environment().putAll(environment); + Process process = processBuilder.start(); try (OutputStream processStdin = process.getOutputStream()) { processStdin.write(serverUrl.getBytes(StandardCharsets.UTF_8)); diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/ContainerizerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/ContainerizerTest.java index bda882cb44..6d1b34db69 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/api/ContainerizerTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/ContainerizerTest.java @@ -17,6 +17,7 @@ package com.google.cloud.tools.jib.api; import com.google.cloud.tools.jib.configuration.ImageConfiguration; +import com.google.cloud.tools.jib.docker.AnotherDockerClient; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.MoreExecutors; import java.io.IOException; @@ -36,11 +37,13 @@ public class ContainerizerTest { public void testTo() throws CacheDirectoryCreationException, InvalidImageReferenceException { RegistryImage registryImage = RegistryImage.named("registry/image"); DockerDaemonImage dockerDaemonImage = DockerDaemonImage.named("daemon/image"); - TarImage tarImage = TarImage.at(Paths.get("ignored")).named("tar/iamge"); + TarImage tarImage = TarImage.at(Paths.get("ignored")).named("tar/image"); + DockerClient dockerClient = new AnotherDockerClient(); verifyTo(Containerizer.to(registryImage)); verifyTo(Containerizer.to(dockerDaemonImage)); verifyTo(Containerizer.to(tarImage)); + verifyTo(Containerizer.to(dockerClient, dockerDaemonImage)); } private void verifyTo(Containerizer containerizer) throws CacheDirectoryCreationException { @@ -104,10 +107,10 @@ public void testGetImageConfiguration_registryImage() throws InvalidImageReferen @Test public void testGetImageConfiguration_dockerDaemonImage() throws InvalidImageReferenceException { - Containerizer containerizer = Containerizer.to(DockerDaemonImage.named("docker/deamon/image")); + Containerizer containerizer = Containerizer.to(DockerDaemonImage.named("docker/daemon/image")); ImageConfiguration imageConfiguration = containerizer.getImageConfiguration(); - Assert.assertEquals("docker/deamon/image", imageConfiguration.getImage().toString()); + Assert.assertEquals("docker/daemon/image", imageConfiguration.getImage().toString()); Assert.assertEquals(0, imageConfiguration.getCredentialRetrievers().size()); } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerClientResolverTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerClientResolverTest.java new file mode 100644 index 0000000000..17c6dc6ad0 --- /dev/null +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerClientResolverTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.api; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.tools.jib.docker.AnotherDockerClient; +import com.google.cloud.tools.jib.docker.DockerClientResolver; +import java.util.Collections; +import java.util.Optional; +import org.junit.Test; + +/** Tests for {@link DockerClientResolver}. */ +public class DockerClientResolverTest { + + @Test + public void testDockerClientIsReturned() { + Optional dockerClient = + DockerClientResolver.resolve(Collections.singletonMap("test", "true")); + assertThat(dockerClient.get()).isInstanceOf(AnotherDockerClient.class); + } +} diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerDaemonImageTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerDaemonImageTest.java index 52ae1db7d8..0ffd4dd078 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerDaemonImageTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/DockerDaemonImageTest.java @@ -16,7 +16,7 @@ package com.google.cloud.tools.jib.api; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.docker.CliDockerClient; import com.google.common.collect.ImmutableMap; import java.nio.file.Paths; import org.junit.Assert; @@ -31,7 +31,7 @@ public void testGetters_default() throws InvalidImageReferenceException { Assert.assertEquals("docker/daemon/image", dockerDaemonImage.getImageReference().toString()); Assert.assertEquals( - DockerClient.DEFAULT_DOCKER_CLIENT, dockerDaemonImage.getDockerExecutable()); + CliDockerClient.DEFAULT_DOCKER_CLIENT, dockerDaemonImage.getDockerExecutable()); Assert.assertEquals(0, dockerDaemonImage.getDockerEnvironment().size()); } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java index 607a3e9e95..cb0e4ec3a8 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java @@ -16,6 +16,11 @@ package com.google.cloud.tools.jib.api; +import static org.mockito.Mockito.when; + +import com.google.cloud.tools.jib.builder.steps.BuildResult; +import com.google.cloud.tools.jib.configuration.BuildContext; +import com.google.cloud.tools.jib.configuration.ImageConfiguration; import com.google.common.collect.ImmutableSet; import java.security.DigestException; import java.util.Set; @@ -24,6 +29,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; /** Tests for {@link JibContainer}. */ public class JibContainerTest { @@ -53,18 +59,19 @@ public void setUp() throws DigestException, InvalidImageReferenceException { @Test public void testCreation() { - JibContainer container = new JibContainer(targetImage1, digest1, digest2, tags1); + JibContainer container = new JibContainer(targetImage1, digest1, digest2, tags1, true); Assert.assertEquals(targetImage1, container.getTargetImage()); Assert.assertEquals(digest1, container.getDigest()); Assert.assertEquals(digest2, container.getImageId()); Assert.assertEquals(tags1, container.getTags()); + Assert.assertTrue(container.isImagePushed()); } @Test public void testEquality() { - JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1); - JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1); + JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1, true); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1, true); Assert.assertEquals(container1, container2); Assert.assertEquals(container1.hashCode(), container2.hashCode()); @@ -72,8 +79,8 @@ public void testEquality() { @Test public void testEquality_differentTargetImage() { - JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1); - JibContainer container2 = new JibContainer(targetImage2, digest1, digest2, tags1); + JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1, true); + JibContainer container2 = new JibContainer(targetImage2, digest1, digest2, tags1, true); Assert.assertNotEquals(container1, container2); Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); @@ -81,8 +88,8 @@ public void testEquality_differentTargetImage() { @Test public void testEquality_differentImageDigest() { - JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1); - JibContainer container2 = new JibContainer(targetImage1, digest2, digest2, tags1); + JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1, true); + JibContainer container2 = new JibContainer(targetImage1, digest2, digest2, tags1, true); Assert.assertNotEquals(container1, container2); Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); @@ -90,8 +97,8 @@ public void testEquality_differentImageDigest() { @Test public void testEquality_differentImageId() { - JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1); - JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1); + JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1, true); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1, true); Assert.assertNotEquals(container1, container2); Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); @@ -99,10 +106,40 @@ public void testEquality_differentImageId() { @Test public void testEquality_differentTags() { - JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1); - JibContainer container2 = new JibContainer(targetImage1, digest1, digest1, tags2); + JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1, true); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest1, tags2, true); + + Assert.assertNotEquals(container1, container2); + Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); + } + + @Test + public void testEquality_differentImagePushed() { + JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1, true); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest1, tags1, false); Assert.assertNotEquals(container1, container2); Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); } + + @Test + public void testCreation_withBuildContextAndBuildResult() { + BuildResult buildResult = Mockito.mock(BuildResult.class); + BuildContext buildContext = Mockito.mock(BuildContext.class); + ImageConfiguration mockTargetConfiguration = Mockito.mock(ImageConfiguration.class); + + when(buildResult.getImageDigest()).thenReturn(digest1); + when(buildResult.getImageId()).thenReturn(digest1); + when(buildResult.isImagePushed()).thenReturn(true); + when(mockTargetConfiguration.getImage()).thenReturn(targetImage1); + when(buildContext.getTargetImageConfiguration()).thenReturn(mockTargetConfiguration); + when(buildContext.getAllTargetImageTags()).thenReturn(ImmutableSet.copyOf(tags1)); + + JibContainer container = JibContainer.from(buildContext, buildResult); + Assert.assertEquals(targetImage1, container.getTargetImage()); + Assert.assertEquals(digest1, container.getDigest()); + Assert.assertEquals(digest1, container.getImageId()); + Assert.assertEquals(tags1, container.getTags()); + Assert.assertTrue(container.isImagePushed()); + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildImageStepTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildImageStepTest.java index 38fd2058ce..b996946a0a 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildImageStepTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildImageStepTest.java @@ -146,7 +146,7 @@ public void test_basicCase() { .call(); Assert.assertEquals("root", image.getUser()); Assert.assertEquals( - testDescriptorDigest, image.getLayers().asList().get(0).getBlobDescriptor().getDigest()); + testDescriptorDigest, image.getLayers().get(0).getBlobDescriptor().getDigest()); } @Test diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildResultTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildResultTest.java index 5397c93787..3c42d6e091 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildResultTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildResultTest.java @@ -31,6 +31,7 @@ public class BuildResultTest { private DescriptorDigest digest1; private DescriptorDigest digest2; private DescriptorDigest id; + private DescriptorDigest id2; @Before public void setUp() throws DigestException { @@ -43,24 +44,37 @@ public void setUp() throws DigestException { id = DescriptorDigest.fromDigest( "sha256:9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"); + + id2 = + DescriptorDigest.fromDigest( + "sha256:1234543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"); } @Test public void testCreated() { - BuildResult container = new BuildResult(digest1, id); + BuildResult container = new BuildResult(digest1, id, true); Assert.assertEquals(digest1, container.getImageDigest()); Assert.assertEquals(id, container.getImageId()); + Assert.assertTrue(container.isImagePushed()); } @Test public void testEquality() { - BuildResult container1 = new BuildResult(digest1, id); - BuildResult container2 = new BuildResult(digest1, id); - BuildResult container3 = new BuildResult(digest2, id); + BuildResult container1 = new BuildResult(digest1, id, true); + BuildResult container2 = new BuildResult(digest1, id, true); + BuildResult container3 = new BuildResult(digest2, id, true); + BuildResult container4 = new BuildResult(digest1, id, false); + BuildResult container5 = new BuildResult(digest1, id2, false); Assert.assertEquals(container1, container2); + Assert.assertEquals(container1, container1); + Assert.assertNotEquals(container1, container5); + Assert.assertNotEquals(container1, new Object()); + Assert.assertEquals(container1.hashCode(), container2.hashCode()); + Assert.assertEquals(container1.hashCode(), container4.hashCode()); Assert.assertNotEquals(container1, container3); + Assert.assertNotEquals(container1, container4); } @Test diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageStepsTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageStepsTest.java index 1d68926631..97bd9f1217 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageStepsTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageStepsTest.java @@ -17,12 +17,13 @@ package com.google.cloud.tools.jib.builder.steps; import com.google.cloud.tools.jib.api.CacheDirectoryCreationException; +import com.google.cloud.tools.jib.api.ImageDetails; import com.google.cloud.tools.jib.builder.ProgressEventDispatcher; import com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage; import com.google.cloud.tools.jib.cache.Cache; import com.google.cloud.tools.jib.cache.CacheCorruptedException; import com.google.cloud.tools.jib.configuration.BuildContext; -import com.google.cloud.tools.jib.docker.DockerClient.DockerImageDetails; +import com.google.cloud.tools.jib.docker.CliDockerClient; import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.json.JsonTemplateMapper; @@ -141,8 +142,8 @@ public void testGetCachedDockerImage() + "\"RootFS\": { \"Layers\": [" + " \"sha256:5e701122d3347fae0758cd5b7f0692c686fcd07b0e7fd9c4a125fbdbbedc04dd\"," + " \"sha256:f1ac3015bcbf0ada4750d728626eb10f0f585199e2b667dcd79e49f0e926178e\" ] } }"; - DockerImageDetails dockerImageDetails = - JsonTemplateMapper.readJson(dockerInspectJson, DockerImageDetails.class); + ImageDetails dockerImageDetails = + JsonTemplateMapper.readJson(dockerInspectJson, CliDockerClient.DockerImageDetails.class); Path cachePath = temporaryFolder.newFolder("cache").toPath(); Files.createDirectories(cachePath.resolve("local/config")); Cache cache = Cache.withDirectory(cachePath); diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java index 05e026fe96..d6bcb38475 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java @@ -16,14 +16,16 @@ package com.google.cloud.tools.jib.builder.steps; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + import com.google.cloud.tools.jib.api.ImageReference; -import com.google.cloud.tools.jib.api.LogEvent; import com.google.cloud.tools.jib.api.buildplan.Platform; import com.google.cloud.tools.jib.configuration.BuildContext; import com.google.cloud.tools.jib.configuration.ContainerConfiguration; import com.google.cloud.tools.jib.configuration.ImageConfiguration; -import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate; +import com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException; import com.google.common.collect.ImmutableSet; import java.nio.file.Path; import java.nio.file.Paths; @@ -40,13 +42,11 @@ public class PlatformCheckerTest { @Mock private BuildContext buildContext; @Mock private ContainerConfiguration containerConfig; - @Mock private EventHandlers eventHandlers; @Before public void setUp() { Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(ImageReference.scratch()).build()); - Mockito.when(buildContext.getEventHandlers()).thenReturn(eventHandlers); Mockito.when(buildContext.getContainerConfiguration()).thenReturn(containerConfig); } @@ -58,39 +58,43 @@ public void testCheckManifestPlatform_mismatch() { ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate(); containerConfigJson.setArchitecture("actual arch"); containerConfigJson.setOs("actual OS"); - - PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson); - - Mockito.verify(eventHandlers) - .dispatch( - LogEvent.warn( - "the configured platform (configured arch/configured OS) doesn't match the " - + "platform (actual arch/actual OS) of the base image (scratch)")); + Exception ex = + assertThrows( + PlatformNotFoundInBaseImageException.class, + () -> PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson)); + assertThat(ex) + .hasMessageThat() + .isEqualTo( + "the configured platform (configured arch/configured OS) doesn't match the " + + "platform (actual arch/actual OS) of the base image (scratch)"); } @Test - public void testCheckManifestPlatform_noWarningIfDefaultAmd64Linux() { + public void testCheckManifestPlatform_noExceptionIfDefaultAmd64Linux() + throws PlatformNotFoundInBaseImageException { Mockito.when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"))); ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate(); containerConfigJson.setArchitecture("actual arch"); containerConfigJson.setOs("actual OS"); - PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson); - - Mockito.verifyNoInteractions(eventHandlers); } @Test public void testCheckManifestPlatform_multiplePlatformsConfigured() { Mockito.when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"), new Platform("arch", "os"))); - - PlatformChecker.checkManifestPlatform(buildContext, new ContainerConfigurationTemplate()); - - Mockito.verify(eventHandlers) - .dispatch(LogEvent.warn("platforms configured, but 'scratch' is not a manifest list")); + Exception ex = + assertThrows( + PlatformNotFoundInBaseImageException.class, + () -> + PlatformChecker.checkManifestPlatform( + buildContext, new ContainerConfigurationTemplate())); + assertThat(ex) + .hasMessageThat() + .isEqualTo( + "cannot build for multiple platforms since the base image 'scratch' is not a manifest list."); } @Test @@ -101,11 +105,17 @@ public void testCheckManifestPlatform_tarBaseImage() { Mockito.when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"), new Platform("arch", "os"))); - PlatformChecker.checkManifestPlatform(buildContext, new ContainerConfigurationTemplate()); - - Mockito.verify(eventHandlers) - .dispatch( - LogEvent.warn( - "platforms configured, but '" + tar.toString() + "' is not a manifest list")); + Exception ex = + assertThrows( + PlatformNotFoundInBaseImageException.class, + () -> + PlatformChecker.checkManifestPlatform( + buildContext, new ContainerConfigurationTemplate())); + assertThat(ex) + .hasMessageThat() + .isEqualTo( + "cannot build for multiple platforms since the base image '" + + tar + + "' is not a manifest list."); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStepTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStepTest.java index cb353f10d4..f3f5005a80 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStepTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStepTest.java @@ -16,6 +16,9 @@ package com.google.cloud.tools.jib.builder.steps; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.eq; + import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; @@ -35,9 +38,14 @@ import com.google.cloud.tools.jib.image.LayerCountMismatchException; import com.google.cloud.tools.jib.image.LayerPropertyNotFoundException; import com.google.cloud.tools.jib.image.json.BadContainerConfigurationFormatException; +import com.google.cloud.tools.jib.image.json.BuildableManifestTemplate; import com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate; import com.google.cloud.tools.jib.image.json.ImageMetadataTemplate; import com.google.cloud.tools.jib.image.json.ManifestAndConfigTemplate; +import com.google.cloud.tools.jib.image.json.ManifestListTemplate; +import com.google.cloud.tools.jib.image.json.ManifestTemplate; +import com.google.cloud.tools.jib.image.json.OciIndexTemplate; +import com.google.cloud.tools.jib.image.json.PlatformNotFoundInBaseImageException; import com.google.cloud.tools.jib.image.json.UnlistedPlatformInManifestListException; import com.google.cloud.tools.jib.image.json.V21ManifestTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate; @@ -151,6 +159,7 @@ public void testCall_digestBaseImage() ImageMetadataTemplate imageMetadata = new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true); ImagesAndRegistryClient result = pullBaseImageStep.call(); Assert.assertEquals("fat system", result.images.get(0).getOs()); @@ -192,6 +201,7 @@ public void testCall_offlineMode_cached() ImageMetadataTemplate imageMetadata = new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true); ImagesAndRegistryClient result = pullBaseImageStep.call(); Assert.assertEquals("fat system", result.images.get(0).getOs()); @@ -201,7 +211,7 @@ public void testCall_offlineMode_cached() } @Test - public void testLookUpPlatformSpecificImageManifest() + public void testLookUpPlatformSpecificDockerImageManifest() throws IOException, UnlistedPlatformInManifestListException { String manifestListJson = " {\n" @@ -240,11 +250,51 @@ public void testLookUpPlatformSpecificImageManifest() "sha256:2222222222222222222222222222222222222222222222222222222222222222", manifestDigest); } + @Test + public void testLookUpPlatformSpecificOciManifest() + throws IOException, UnlistedPlatformInManifestListException { + String manifestListJson = + " {\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" + + " \"size\": 424,\n" + + " \"digest\": \"sha256:1111111111111111111111111111111111111111111111111111111111111111\",\n" + + " \"platform\": {\n" + + " \"architecture\": \"arm64\",\n" + + " \"os\": \"linux\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n" + + " \"size\": 425,\n" + + " \"digest\": \"sha256:2222222222222222222222222222222222222222222222222222222222222222\",\n" + + " \"platform\": {\n" + + " \"architecture\": \"targetArchitecture\",\n" + + " \"os\": \"targetOS\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}"; + + OciIndexTemplate manifestList = + JsonTemplateMapper.readJson(manifestListJson, OciIndexTemplate.class); + + String manifestDigest = + pullBaseImageStep.lookUpPlatformSpecificImageManifest( + manifestList, new Platform("targetArchitecture", "targetOS")); + + Assert.assertEquals( + "sha256:2222222222222222222222222222222222222222222222222222222222222222", manifestDigest); + } + @Test public void testGetCachedBaseImages_emptyCache() throws InvalidImageReferenceException, IOException, CacheCorruptedException, - UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException, - LayerCountMismatchException { + UnlistedPlatformInManifestListException, PlatformNotFoundInBaseImageException, + BadContainerConfigurationFormatException, LayerCountMismatchException { ImageReference imageReference = ImageReference.parse("cat"); Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(imageReference).build()); @@ -253,11 +303,29 @@ public void testGetCachedBaseImages_emptyCache() Assert.assertEquals(Arrays.asList(), pullBaseImageStep.getCachedBaseImages()); } + @Test + public void testGetCachedBaseImages_partiallyCached_emptyListReturned() + throws InvalidImageReferenceException, CacheCorruptedException, IOException, + LayerCountMismatchException, PlatformNotFoundInBaseImageException, + BadContainerConfigurationFormatException, UnlistedPlatformInManifestListException { + ImageReference imageReference = ImageReference.parse("cat"); + Mockito.when(buildContext.getBaseImageConfiguration()) + .thenReturn(ImageConfiguration.builder(imageReference).build()); + ManifestTemplate manifest = Mockito.mock(ManifestTemplate.class); + ImageMetadataTemplate imageMetadata = + new ImageMetadataTemplate( + null, Arrays.asList(new ManifestAndConfigTemplate(manifest, null))); + Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when(cache.areAllLayersCached(manifest)).thenReturn(false); + + assertThat(pullBaseImageStep.getCachedBaseImages()).isEmpty(); + } + @Test public void testGetCachedBaseImages_v21ManifestCached() throws InvalidImageReferenceException, IOException, CacheCorruptedException, UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException, - LayerCountMismatchException, DigestException { + LayerCountMismatchException, DigestException, PlatformNotFoundInBaseImageException { ImageReference imageReference = ImageReference.parse("cat"); Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(imageReference).build()); @@ -272,6 +340,7 @@ public void testGetCachedBaseImages_v21ManifestCached() null, Arrays.asList(new ManifestAndConfigTemplate(v21Manifest, null))); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when(cache.areAllLayersCached(v21Manifest)).thenReturn(true); List images = pullBaseImageStep.getCachedBaseImages(); @@ -283,10 +352,10 @@ public void testGetCachedBaseImages_v21ManifestCached() } @Test - public void testGetCachedBaseImages_v22ManifestCached() + public void testGetCachedBaseImages_manifestCached() throws InvalidImageReferenceException, IOException, CacheCorruptedException, UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException, - LayerCountMismatchException { + LayerCountMismatchException, PlatformNotFoundInBaseImageException { ImageReference imageReference = ImageReference.parse("cat"); Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(imageReference).build()); @@ -296,10 +365,11 @@ public void testGetCachedBaseImages_v22ManifestCached() containerConfigJson.setOs("fat system"); ManifestAndConfigTemplate manifestAndConfig = new ManifestAndConfigTemplate( - new V22ManifestTemplate(), containerConfigJson, "sha256:digest"); + Mockito.mock(BuildableManifestTemplate.class), containerConfigJson, "sha256:digest"); ImageMetadataTemplate imageMetadata = new ImageMetadataTemplate(null, Arrays.asList(manifestAndConfig)); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when(cache.areAllLayersCached(manifestAndConfig.getManifest())).thenReturn(true); List images = pullBaseImageStep.getCachedBaseImages(); @@ -309,10 +379,10 @@ public void testGetCachedBaseImages_v22ManifestCached() } @Test - public void testGetCachedBaseImages_v22ManifestListCached() + public void testGetCachedBaseImages_manifestListCached() throws InvalidImageReferenceException, IOException, CacheCorruptedException, UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException, - LayerCountMismatchException { + LayerCountMismatchException, PlatformNotFoundInBaseImageException { ImageReference imageReference = ImageReference.parse("cat"); Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(imageReference).build()); @@ -322,7 +392,7 @@ public void testGetCachedBaseImages_v22ManifestListCached() containerConfigJson1.setContainerUser("user1"); containerConfigJson2.setContainerUser("user2"); - V22ManifestListTemplate manifestList = Mockito.mock(V22ManifestListTemplate.class); + ManifestListTemplate manifestList = Mockito.mock(ManifestListTemplate.class); Mockito.when(manifestList.getDigestsForPlatform("arch1", "os1")) .thenReturn(Arrays.asList("sha256:digest1")); Mockito.when(manifestList.getDigestsForPlatform("arch2", "os2")) @@ -333,10 +403,20 @@ public void testGetCachedBaseImages_v22ManifestListCached() manifestList, Arrays.asList( new ManifestAndConfigTemplate( - new V22ManifestTemplate(), containerConfigJson1, "sha256:digest1"), + Mockito.mock(BuildableManifestTemplate.class), + containerConfigJson1, + "sha256:digest1"), new ManifestAndConfigTemplate( - new V22ManifestTemplate(), containerConfigJson2, "sha256:digest2"))); + Mockito.mock(BuildableManifestTemplate.class), + containerConfigJson2, + "sha256:digest2"))); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when( + cache.areAllLayersCached(imageMetadata.getManifestsAndConfigs().get(0).getManifest())) + .thenReturn(true); + Mockito.when( + cache.areAllLayersCached(imageMetadata.getManifestsAndConfigs().get(1).getManifest())) + .thenReturn(true); Mockito.when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("arch1", "os1"), new Platform("arch2", "os2"))); @@ -349,15 +429,15 @@ public void testGetCachedBaseImages_v22ManifestListCached() } @Test - public void testGetCachedBaseImages_v22ManifestListCached_partialMatches() + public void testGetCachedBaseImages_manifestListCached_partialMatches() throws InvalidImageReferenceException, IOException, CacheCorruptedException, UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException, - LayerCountMismatchException { + LayerCountMismatchException, PlatformNotFoundInBaseImageException { ImageReference imageReference = ImageReference.parse("cat"); Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(imageReference).build()); - V22ManifestListTemplate manifestList = Mockito.mock(V22ManifestListTemplate.class); + ManifestListTemplate manifestList = Mockito.mock(ManifestListTemplate.class); Mockito.when(manifestList.getDigestsForPlatform("arch1", "os1")) .thenReturn(Arrays.asList("sha256:digest1")); Mockito.when(manifestList.getDigestsForPlatform("arch2", "os2")) @@ -368,10 +448,13 @@ public void testGetCachedBaseImages_v22ManifestListCached_partialMatches() manifestList, Arrays.asList( new ManifestAndConfigTemplate( - new V22ManifestTemplate(), + Mockito.mock(BuildableManifestTemplate.class), new ContainerConfigurationTemplate(), "sha256:digest1"))); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when( + cache.areAllLayersCached(imageMetadata.getManifestsAndConfigs().get(0).getManifest())) + .thenReturn(true); Mockito.when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("arch1", "os1"), new Platform("arch2", "os2"))); @@ -380,15 +463,15 @@ public void testGetCachedBaseImages_v22ManifestListCached_partialMatches() } @Test - public void testGetCachedBaseImages_v22ManifestListCached_onlyPlatforms() + public void testGetCachedBaseImages_manifestListCached_onlyPlatforms() throws InvalidImageReferenceException, IOException, CacheCorruptedException, - UnlistedPlatformInManifestListException, BadContainerConfigurationFormatException, - LayerCountMismatchException { + UnlistedPlatformInManifestListException, PlatformNotFoundInBaseImageException, + BadContainerConfigurationFormatException, LayerCountMismatchException { ImageReference imageReference = ImageReference.parse("cat"); Mockito.when(buildContext.getBaseImageConfiguration()) .thenReturn(ImageConfiguration.builder(imageReference).build()); - V22ManifestListTemplate manifestList = Mockito.mock(V22ManifestListTemplate.class); + ManifestListTemplate manifestList = Mockito.mock(ManifestListTemplate.class); Mockito.when(manifestList.getDigestsForPlatform("target-arch", "target-os")) .thenReturn(Arrays.asList("sha256:target-digest")); @@ -397,10 +480,12 @@ public void testGetCachedBaseImages_v22ManifestListCached_onlyPlatforms() ManifestAndConfigTemplate targetManifestAndConfig = new ManifestAndConfigTemplate( - new V22ManifestTemplate(), containerConfigJson, "sha256:target-digest"); + Mockito.mock(BuildableManifestTemplate.class), + containerConfigJson, + "sha256:target-digest"); ManifestAndConfigTemplate unrelatedManifestAndConfig = new ManifestAndConfigTemplate( - new V22ManifestTemplate(), + Mockito.mock(BuildableManifestTemplate.class), new ContainerConfigurationTemplate(), "sha256:unrelated-digest"); @@ -410,6 +495,7 @@ public void testGetCachedBaseImages_v22ManifestListCached_onlyPlatforms() Arrays.asList( unrelatedManifestAndConfig, targetManifestAndConfig, unrelatedManifestAndConfig)); Mockito.when(cache.retrieveMetadata(imageReference)).thenReturn(Optional.of(imageMetadata)); + Mockito.when(cache.areAllLayersCached(targetManifestAndConfig.getManifest())).thenReturn(true); Mockito.when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("target-arch", "target-os"))); @@ -422,7 +508,8 @@ public void testGetCachedBaseImages_v22ManifestListCached_onlyPlatforms() @Test public void testTryMirrors_noMatchingMirrors() - throws LayerCountMismatchException, BadContainerConfigurationFormatException { + throws LayerCountMismatchException, BadContainerConfigurationFormatException, + PlatformNotFoundInBaseImageException { Mockito.when(imageConfiguration.getImageRegistry()).thenReturn("registry"); Mockito.when(buildContext.getRegistryMirrors()) .thenReturn(ImmutableListMultimap.of("unmatched1", "mirror1", "unmatched2", "mirror2")); @@ -475,8 +562,11 @@ public void testTryMirrors_multipleMirrors() .thenReturn(registryClientFactory); Mockito.when(registryClient.pullManifest(Mockito.any())) .thenThrow(new RegistryException("not found")); + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"))); - RegistryClient.Factory gcrRegistryClientFactory = setUpWorkingRegistryClientFactory(); + RegistryClient.Factory gcrRegistryClientFactory = + setUpWorkingRegistryClientFactoryWithV22ManifestTemplate(); Mockito.when(buildContext.newBaseImageRegistryClientFactory("gcr.io")) .thenReturn(gcrRegistryClientFactory); @@ -513,8 +603,10 @@ public void testCall_allMirrorsFail() .thenReturn(registryClientFactory); Mockito.when(registryClient.pullManifest(Mockito.any())) .thenThrow(new RegistryException("not found")); - - RegistryClient.Factory dockerHubRegistryClientFactory = setUpWorkingRegistryClientFactory(); + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"))); + RegistryClient.Factory dockerHubRegistryClientFactory = + setUpWorkingRegistryClientFactoryWithV22ManifestTemplate(); Mockito.when(buildContext.newBaseImageRegistryClientFactory()) .thenReturn(dockerHubRegistryClientFactory); @@ -536,7 +628,50 @@ public void testCall_allMirrorsFail() .dispatch(LogEvent.debug("failed to get manifest from mirror gcr.io: not found")); } - private static RegistryClient.Factory setUpWorkingRegistryClientFactory() + @Test + public void testCall_ManifestList() + throws InvalidImageReferenceException, IOException, RegistryException, + LayerPropertyNotFoundException, LayerCountMismatchException, + BadContainerConfigurationFormatException, CacheCorruptedException, + CredentialRetrievalException { + Mockito.when(buildContext.getBaseImageConfiguration()) + .thenReturn(ImageConfiguration.builder(ImageReference.parse("multiarch")).build()); + Mockito.when(buildContext.getRegistryMirrors()) + .thenReturn(ImmutableListMultimap.of("registry", "gcr.io")); + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"))); + RegistryClient.Factory dockerHubRegistryClientFactory = + setUpWorkingRegistryClientFactoryWithV22ManifestList(); + Mockito.when(buildContext.newBaseImageRegistryClientFactory()) + .thenReturn(dockerHubRegistryClientFactory); + + ImagesAndRegistryClient result = pullBaseImageStep.call(); + Assert.assertEquals(V22ManifestTemplate.class, result.images.get(0).getImageFormat()); + Assert.assertEquals("linux", result.images.get(0).getOs()); + Assert.assertEquals("amd64", result.images.get(0).getArchitecture()); + } + + @Test(expected = UnlistedPlatformInManifestListException.class) + public void testCall_ManifestList_UnknownArchitecture() + throws InvalidImageReferenceException, IOException, RegistryException, + LayerPropertyNotFoundException, LayerCountMismatchException, + BadContainerConfigurationFormatException, CacheCorruptedException, + CredentialRetrievalException { + Mockito.when(buildContext.getBaseImageConfiguration()) + .thenReturn(ImageConfiguration.builder(ImageReference.parse("multiarch")).build()); + Mockito.when(buildContext.getRegistryMirrors()) + .thenReturn(ImmutableListMultimap.of("registry", "gcr.io")); + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("arm64", "linux"))); + RegistryClient.Factory dockerHubRegistryClientFactory = + setUpWorkingRegistryClientFactoryWithV22ManifestList(); + Mockito.when(buildContext.newBaseImageRegistryClientFactory()) + .thenReturn(dockerHubRegistryClientFactory); + + pullBaseImageStep.call(); + } + + private static RegistryClient.Factory setUpWorkingRegistryClientFactoryWithV22ManifestTemplate() throws IOException, RegistryException { DescriptorDigest digest = Mockito.mock(DescriptorDigest.class); V22ManifestTemplate manifest = new V22ManifestTemplate(); @@ -557,4 +692,37 @@ private static RegistryClient.Factory setUpWorkingRegistryClientFactory() }); return clientFactory; } + + private static RegistryClient.Factory setUpWorkingRegistryClientFactoryWithV22ManifestList() + throws IOException, RegistryException { + DescriptorDigest digest = Mockito.mock(DescriptorDigest.class); + V22ManifestListTemplate manifestList = new V22ManifestListTemplate(); + V22ManifestListTemplate.ManifestDescriptorTemplate platformManifest = + new V22ManifestListTemplate.ManifestDescriptorTemplate(); + platformManifest.setMediaType(V22ManifestTemplate.MANIFEST_MEDIA_TYPE); + platformManifest.setSize(1234); + platformManifest.setDigest("sha256:aaaaaaa"); + platformManifest.setPlatform("amd64", "linux"); + manifestList.addManifest(platformManifest); + + V22ManifestTemplate manifest = new V22ManifestTemplate(); + manifest.setContainerConfiguration(1234, digest); + + RegistryClient.Factory clientFactory = Mockito.mock(RegistryClient.Factory.class); + RegistryClient client = Mockito.mock(RegistryClient.class); + Mockito.when(clientFactory.newRegistryClient()).thenReturn(client); + Mockito.when(client.pullManifest(eq("sha256:aaaaaaa"))) + .thenReturn(new ManifestAndDigest<>(manifest, digest)); + Mockito.when(client.pullManifest(eq("latest"))) + .thenReturn(new ManifestAndDigest<>(manifestList, digest)); + // mocking pulling container config json + Mockito.when(client.pullBlob(Mockito.any(), Mockito.any(), Mockito.any())) + .then( + invocation -> { + Consumer blobSizeListener = invocation.getArgument(1); + blobSizeListener.accept(1L); + return Blobs.from("{}"); + }); + return clientFactory; + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PushImageStepTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PushImageStepTest.java index 7104aec130..b06096938a 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PushImageStepTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PushImageStepTest.java @@ -16,27 +16,37 @@ package com.google.cloud.tools.jib.builder.steps; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.api.RegistryException; import com.google.cloud.tools.jib.api.buildplan.Platform; +import com.google.cloud.tools.jib.blob.BlobDescriptor; import com.google.cloud.tools.jib.builder.ProgressEventDispatcher; import com.google.cloud.tools.jib.configuration.BuildContext; import com.google.cloud.tools.jib.configuration.ContainerConfiguration; import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.global.JibSystemProperties; +import com.google.cloud.tools.jib.image.Image; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate; +import com.google.cloud.tools.jib.image.json.V22ManifestTemplate; import com.google.cloud.tools.jib.registry.RegistryClient; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.io.IOException; -import org.junit.Assert; +import java.util.List; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; /** Tests for {@link PushImageStep}. */ @@ -50,20 +60,21 @@ public class PushImageStepTest { @Mock private BuildContext buildContext; @Mock private RegistryClient registryClient; @Mock private ContainerConfiguration containerConfig; + @Mock private DescriptorDigest mockDescriptorDigest; private final V22ManifestListTemplate manifestList = new V22ManifestListTemplate(); @Before public void setUp() { - Mockito.when(buildContext.getAllTargetImageTags()).thenReturn(ImmutableSet.of("tag1", "tag2")); - Mockito.when(buildContext.getEventHandlers()).thenReturn(EventHandlers.NONE); - Mockito.when(buildContext.getContainerConfiguration()).thenReturn(containerConfig); - Mockito.when(containerConfig.getPlatforms()) + when(buildContext.getAllTargetImageTags()).thenReturn(ImmutableSet.of("tag1", "tag2")); + when(buildContext.getEventHandlers()).thenReturn(EventHandlers.NONE); + when(buildContext.getContainerConfiguration()).thenReturn(containerConfig); + doReturn(V22ManifestTemplate.class).when(buildContext).getTargetFormat(); + when(containerConfig.getPlatforms()) .thenReturn( ImmutableSet.of(new Platform("amd64", "linux"), new Platform("arm64", "windows"))); - Mockito.when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong())) - .thenReturn(progressDispatcher); - Mockito.when(progressDispatcher.newChildProducer()).thenReturn(progressDispatcherFactory); + when(progressDispatcherFactory.create(anyString(), anyLong())).thenReturn(progressDispatcher); + when(progressDispatcher.newChildProducer()).thenReturn(progressDispatcherFactory); ManifestDescriptorTemplate manifest = new ManifestDescriptorTemplate(); manifest.setSize(100); @@ -73,40 +84,86 @@ public void setUp() { @Test public void testMakeListForManifestList() throws IOException, RegistryException { - ImmutableList pushImageStepList = + List pushImageStepList = PushImageStep.makeListForManifestList( buildContext, progressDispatcherFactory, registryClient, manifestList, false); - Assert.assertEquals(2, pushImageStepList.size()); + assertThat(pushImageStepList).hasSize(2); for (PushImageStep pushImageStep : pushImageStepList) { BuildResult buildResult = pushImageStep.call(); - Assert.assertEquals( - "sha256:64303e82b8a80ef20475dc7f807b81f172cacce1a59191927f3a7ea5222f38ae", - buildResult.getImageDigest().toString()); - Assert.assertEquals( - "sha256:64303e82b8a80ef20475dc7f807b81f172cacce1a59191927f3a7ea5222f38ae", - buildResult.getImageId().toString()); + assertThat(buildResult.getImageDigest().toString()) + .isEqualTo("sha256:64303e82b8a80ef20475dc7f807b81f172cacce1a59191927f3a7ea5222f38ae"); + assertThat(buildResult.getImageId().toString()) + .isEqualTo("sha256:64303e82b8a80ef20475dc7f807b81f172cacce1a59191927f3a7ea5222f38ae"); } } + @Test + public void testMakeList_multiPlatform_platformTags() throws IOException, RegistryException { + Image image = Image.builder(V22ManifestTemplate.class).setArchitecture("wasm").build(); + + when(buildContext.getEnablePlatformTags()).thenReturn(true); + + List pushImageStepList = + PushImageStep.makeList( + buildContext, + progressDispatcherFactory, + registryClient, + new BlobDescriptor(mockDescriptorDigest), + image, + false); + + ArgumentCaptor tagCatcher = ArgumentCaptor.forClass(String.class); + when(registryClient.pushManifest(any(), tagCatcher.capture())).thenReturn(null); + + assertThat(pushImageStepList).hasSize(2); + pushImageStepList.get(0).call(); + pushImageStepList.get(1).call(); + + assertThat(tagCatcher.getAllValues()).containsExactly("tag1-wasm", "tag2-wasm"); + } + + @Test + public void testMakeList_multiPlatform_nonPlatformTags() throws IOException, RegistryException { + Image image = Image.builder(V22ManifestTemplate.class).setArchitecture("wasm").build(); + when(buildContext.getEnablePlatformTags()).thenReturn(false); + + List pushImageStepList = + PushImageStep.makeList( + buildContext, + progressDispatcherFactory, + registryClient, + new BlobDescriptor(mockDescriptorDigest), + image, + false); + + ArgumentCaptor tagCatcher = ArgumentCaptor.forClass(String.class); + when(registryClient.pushManifest(any(), tagCatcher.capture())).thenReturn(null); + + assertThat(pushImageStepList).hasSize(1); + pushImageStepList.get(0).call(); + assertThat(tagCatcher.getAllValues()) + .containsExactly("sha256:0dd75658cf52608fbd72eb95ff5fc5946966258c3676b35d336bfcc7ac5006f1"); + } + @Test public void testMakeListForManifestList_singlePlatform() throws IOException { - Mockito.when(containerConfig.getPlatforms()) + when(containerConfig.getPlatforms()) .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"))); - ImmutableList pushImageStepList = + List pushImageStepList = PushImageStep.makeListForManifestList( buildContext, progressDispatcherFactory, registryClient, manifestList, false); - Assert.assertEquals(0, pushImageStepList.size()); + assertThat(pushImageStepList).isEmpty(); } @Test public void testMakeListForManifestList_manifestListAlreadyExists() throws IOException { System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, "true"); - ImmutableList pushImageStepList = + List pushImageStepList = PushImageStep.makeListForManifestList( buildContext, progressDispatcherFactory, registryClient, manifestList, true); - Assert.assertEquals(0, pushImageStepList.size()); + assertThat(pushImageStepList).isEmpty(); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java index c7c63afa34..6468ee051b 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java @@ -16,13 +16,21 @@ package com.google.cloud.tools.jib.builder.steps; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.builder.ProgressEventDispatcher; import com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient; import com.google.cloud.tools.jib.configuration.BuildContext; +import com.google.cloud.tools.jib.event.EventHandlers; +import com.google.cloud.tools.jib.global.JibSystemProperties; import com.google.cloud.tools.jib.image.DigestOnlyLayer; import com.google.cloud.tools.jib.image.Image; +import com.google.cloud.tools.jib.image.json.ManifestTemplate; +import com.google.cloud.tools.jib.registry.ManifestAndDigest; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ForwardingExecutorService; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -30,6 +38,7 @@ import java.security.DigestException; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -76,9 +85,14 @@ protected ExecutorService delegate() { } @Mock private BuildContext buildContext; + @Mock private EventHandlers eventHandlers; @Mock private ProgressEventDispatcher.Factory progressDispatcherFactory; @Mock private ProgressEventDispatcher progressDispatcher; @Mock private ExecutorService executorService; + @Mock private Image builtArm64AndLinuxImage; + @Mock private Image builtAmd64AndWindowsImage; + @Mock private Image baseImage1; + @Mock private Image baseImage2; private StepsRunner stepsRunner; @@ -86,14 +100,14 @@ protected ExecutorService delegate() { public void setup() { stepsRunner = new StepsRunner(new MockListeningExecutorService(), buildContext); - Mockito.when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong())) + when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong())) .thenReturn(progressDispatcher); } @Test public void testObtainBaseImageLayers_skipObtainingDuplicateLayers() throws DigestException, InterruptedException, ExecutionException { - Mockito.when(executorService.submit(Mockito.any(PullBaseImageStep.class))) + when(executorService.submit(Mockito.any(PullBaseImageStep.class))) .thenReturn(Futures.immediateFuture(new ImagesAndRegistryClient(null, null))); // Pretend that a thread pulling base images returned some (meaningless) result. stepsRunner.pullBaseImages(progressDispatcherFactory); @@ -114,7 +128,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers() PreparedLayer preparedLayer1 = Mockito.mock(PreparedLayer.class); PreparedLayer preparedLayer2 = Mockito.mock(PreparedLayer.class); PreparedLayer preparedLayer3 = Mockito.mock(PreparedLayer.class); - Mockito.when(executorService.submit(Mockito.any(ObtainBaseImageLayerStep.class))) + when(executorService.submit(Mockito.any(ObtainBaseImageLayerStep.class))) .thenReturn(Futures.immediateFuture(preparedLayer1)) .thenReturn(Futures.immediateFuture(preparedLayer2)) .thenReturn(Futures.immediateFuture(preparedLayer3)); @@ -123,7 +137,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers() // 1. Should schedule two threads to obtain new layers. Image image = Mockito.mock(Image.class); - Mockito.when(image.getLayers()).thenReturn(ImmutableList.of(layer1, layer2)); + when(image.getLayers()).thenReturn(ImmutableList.of(layer1, layer2)); stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory); Assert.assertEquals(2, preparedLayersCache.size()); // two new layers cached @@ -137,7 +151,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers() Assert.assertEquals(preparedLayer2, preparedLayersCache.get(digest2).get()); // 3. Another image with one duplicate layer. - Mockito.when(image.getLayers()).thenReturn(ImmutableList.of(layer3, layer2)); + when(image.getLayers()).thenReturn(ImmutableList.of(layer3, layer2)); stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory); Assert.assertEquals(3, preparedLayersCache.size()); // one new layer cached Assert.assertEquals(preparedLayer1, preparedLayersCache.get(digest1).get()); @@ -148,4 +162,127 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers() Mockito.verify(executorService, Mockito.times(3)) .submit(Mockito.any(ObtainBaseImageLayerStep.class)); } + + @Test + public void testIsImagePushed_skipExistingEnabledAndManifestPresent() { + Optional> manifestResult = Mockito.mock(Optional.class); + when(manifestResult.isPresent()).thenReturn(true); + System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, "true"); + + Assert.assertFalse(stepsRunner.isImagePushed(manifestResult)); + } + + @Test + public void testIsImagePushed_skipExistingImageDisabledAndManifestPresent() { + Optional> manifestResult = Mockito.mock(Optional.class); + System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, "false"); + + Assert.assertTrue(stepsRunner.isImagePushed(manifestResult)); + } + + @Test + public void testIsImagePushed_skipExistingImageEnabledAndManifestNotPresent() { + Optional> manifestResult = Mockito.mock(Optional.class); + System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, "true"); + when(manifestResult.isPresent()).thenReturn(false); + + Assert.assertTrue(stepsRunner.isImagePushed(manifestResult)); + } + + @Test + public void testFetchBuildImageForLocalBuild_matchingOsAndArch() + throws ExecutionException, InterruptedException { + when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64"); + when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn("amd64"); + when(builtAmd64AndWindowsImage.getOs()).thenReturn("windows"); + when(executorService.submit(Mockito.any(Callable.class))) + .thenReturn( + Futures.immediateFuture( + ImmutableMap.of( + baseImage1, + Futures.immediateFuture(builtArm64AndLinuxImage), + baseImage2, + Futures.immediateFuture(builtAmd64AndWindowsImage)))); + stepsRunner.buildImages(progressDispatcherFactory); + + Image expectedImage = + stepsRunner.fetchBuiltImageForLocalBuild("windows", "amd64", eventHandlers); + + assertThat(expectedImage.getOs()).isEqualTo("windows"); + assertThat(expectedImage.getArchitecture()).isEqualTo("amd64"); + } + + @Test + public void testFetchBuildImageForLocalBuild_differentOs_buildImageForFirstPlatform() + throws ExecutionException, InterruptedException { + when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64"); + when(builtArm64AndLinuxImage.getOs()).thenReturn("linux"); + when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn("amd64"); + when(executorService.submit(Mockito.any(Callable.class))) + .thenReturn( + Futures.immediateFuture( + ImmutableMap.of( + baseImage1, + Futures.immediateFuture(builtArm64AndLinuxImage), + baseImage2, + Futures.immediateFuture(builtAmd64AndWindowsImage)))); + stepsRunner.buildImages(progressDispatcherFactory); + + Image expectedImage = stepsRunner.fetchBuiltImageForLocalBuild("os", "arm64", eventHandlers); + + assertThat(expectedImage.getOs()).isEqualTo("linux"); + assertThat(expectedImage.getArchitecture()).isEqualTo("arm64"); + } + + @Test + public void testFetchBuildImageForLocalBuild_differentArch_buildImageForFirstPlatform() + throws ExecutionException, InterruptedException { + when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64"); + when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn("amd64"); + when(executorService.submit(Mockito.any(Callable.class))) + .thenReturn( + Futures.immediateFuture( + ImmutableMap.of( + baseImage1, + Futures.immediateFuture(builtArm64AndLinuxImage), + baseImage2, + Futures.immediateFuture(builtAmd64AndWindowsImage)))); + stepsRunner.buildImages(progressDispatcherFactory); + + Image expectedImage = stepsRunner.fetchBuiltImageForLocalBuild("linux", "arch", eventHandlers); + + assertThat(expectedImage.getArchitecture()).isEqualTo("arm64"); + } + + @Test + public void testFetchBuildImageForLocalBuild_singleImage_imagePlatformDifferentFromDockerEnv() + throws ExecutionException, InterruptedException { + when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64"); + when(builtArm64AndLinuxImage.getOs()).thenReturn("linux"); + when(executorService.submit(Mockito.any(Callable.class))) + .thenReturn( + Futures.immediateFuture( + ImmutableMap.of(baseImage1, Futures.immediateFuture(builtArm64AndLinuxImage)))); + stepsRunner.buildImages(progressDispatcherFactory); + + Image expectedImage = stepsRunner.fetchBuiltImageForLocalBuild("linux", "amd64", eventHandlers); + + assertThat(expectedImage.getOs()).isEqualTo("linux"); + assertThat(expectedImage.getArchitecture()).isEqualTo("arm64"); + } + + @Test + public void testNormalizeArchitecture_aarch64() { + assertThat(stepsRunner.normalizeArchitecture("aarch64")).isEqualTo("arm64"); + } + + @Test + public void testNormalizeArchitecture_x86_64() { + assertThat(stepsRunner.normalizeArchitecture("x86_64")).isEqualTo("amd64"); + } + + @Test + public void testNormalizeArchitecture_arm() { + assertThat(stepsRunner.normalizeArchitecture("arm")).isEqualTo("arm"); + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageReaderTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageReaderTest.java index ca6a663e4a..47f7ff696f 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageReaderTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageReaderTest.java @@ -16,6 +16,8 @@ package com.google.cloud.tools.jib.cache; +import static com.google.common.truth.Truth.assertThat; + import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.blob.Blobs; @@ -297,7 +299,7 @@ public void testRetrieveMetadata_ociImageIndex() MatcherAssert.assertThat( metadata.getManifestList(), CoreMatchers.instanceOf(OciIndexTemplate.class)); - List manifestDescriptors = + List manifestDescriptors = ((OciIndexTemplate) metadata.getManifestList()).getManifests(); Assert.assertEquals(1, manifestDescriptors.size()); @@ -638,4 +640,45 @@ public void testVerifyImageMetadata_validOciImageIndex() throws CacheCorruptedEx CacheStorageReader.verifyImageMetadata(metadata, Paths.get("/cache/dir")); // should pass without CacheCorruptedException } + + @Test + public void testAllLayersCached_v21SingleManifest() + throws IOException, CacheCorruptedException, DigestException, URISyntaxException { + setupCachedMetadataV21(cacheDirectory); + ImageMetadataTemplate metadata = + cacheStorageReader.retrieveMetadata(ImageReference.of("test", "image", "tag")).get(); + V21ManifestTemplate manifest = + (V21ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest(); + DescriptorDigest firstLayerDigest = manifest.getLayerDigests().get(0); + DescriptorDigest secondLayerDigest = manifest.getLayerDigests().get(1); + + // Create only one of the layer directories so that layers are only partially cached. + Files.createDirectories(cacheStorageFiles.getLayerDirectory(firstLayerDigest)); + boolean checkWithPartialLayersCached = cacheStorageReader.areAllLayersCached(manifest); + // Create the other layer directory so that all layers are cached. + Files.createDirectories(cacheStorageFiles.getLayerDirectory(secondLayerDigest)); + boolean checkWithAllLayersCached = cacheStorageReader.areAllLayersCached(manifest); + + assertThat(checkWithPartialLayersCached).isFalse(); + assertThat(checkWithAllLayersCached).isTrue(); + } + + @Test + public void testAllLayersCached_v22SingleManifest() + throws IOException, CacheCorruptedException, DigestException, URISyntaxException { + setupCachedMetadataV22(cacheDirectory); + ImageMetadataTemplate metadata = + cacheStorageReader.retrieveMetadata(ImageReference.of("test", "image", "tag")).get(); + V22ManifestTemplate manifest = + (V22ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest(); + DescriptorDigest layerDigest = manifest.getLayers().get(0).getDigest(); + + boolean checkBeforeLayerCached = cacheStorageReader.areAllLayersCached(manifest); + // Create the single layer directory so that all layers are cached. + Files.createDirectories(cacheStorageFiles.getLayerDirectory(layerDigest)); + boolean checkAfterLayerCached = cacheStorageReader.areAllLayersCached(manifest); + + assertThat(checkBeforeLayerCached).isFalse(); + assertThat(checkAfterLayerCached).isTrue(); + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageWriterTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageWriterTest.java index de9280cbf9..63fa3e29e1 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageWriterTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/cache/CacheStorageWriterTest.java @@ -41,6 +41,7 @@ import com.google.common.io.Resources; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.OutputStream; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -50,8 +51,9 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import org.apache.commons.compress.compressors.CompressorException; +import org.apache.commons.compress.compressors.CompressorStreamFactory; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.Assert; @@ -68,6 +70,7 @@ private static BlobDescriptor getDigest(Blob blob) throws IOException { } private static Blob compress(Blob blob) { + // Don't use GzipCompressorOutputStream, which has different defaults than GZIPOutputStream return Blobs.from( outputStream -> { try (GZIPOutputStream compressorStream = new GZIPOutputStream(outputStream)) { @@ -77,8 +80,28 @@ private static Blob compress(Blob blob) { false); } + private static Blob compress(Blob blob, String compressorName) { + return Blobs.from( + outputStream -> { + try (OutputStream compressorStream = + CompressorStreamFactory.getSingleton() + .createCompressorOutputStream(compressorName, outputStream)) { + blob.writeTo(compressorStream); + } catch (CompressorException e) { + throw new RuntimeException(e); + } + }, + false); + } + private static Blob decompress(Blob blob) throws IOException { - return Blobs.from(new GZIPInputStream(new ByteArrayInputStream(Blobs.writeToByteArray(blob)))); + try { + return Blobs.from( + CompressorStreamFactory.getSingleton() + .createCompressorInputStream(new ByteArrayInputStream(Blobs.writeToByteArray(blob)))); + } catch (CompressorException e) { + throw new IOException(e); + } } private static T loadJsonResource(String path, Class jsonClass) @@ -103,10 +126,27 @@ public void setUp() throws IOException { @Test public void testWriteCompressed() throws IOException { Blob uncompressedLayerBlob = Blobs.from("uncompressedLayerBlob"); + Blob compressedLayerBlob = compress(uncompressedLayerBlob); + CachedLayer cachedLayer = cacheStorageWriter.writeCompressed(compressedLayerBlob); - CachedLayer cachedLayer = cacheStorageWriter.writeCompressed(compress(uncompressedLayerBlob)); + verifyCachedLayer(cachedLayer, uncompressedLayerBlob, compressedLayerBlob); + } - verifyCachedLayer(cachedLayer, uncompressedLayerBlob); + @Test + public void testWriteZstdCompressed() throws IOException { + Blob uncompressedLayerBlob = Blobs.from("uncompressedLayerBlob"); + Blob compressedLayerBlob = compress(uncompressedLayerBlob, CompressorStreamFactory.ZSTANDARD); + + CachedLayer cachedLayer = cacheStorageWriter.writeCompressed(compressedLayerBlob); + + verifyCachedLayer(cachedLayer, uncompressedLayerBlob, compressedLayerBlob); + } + + @Test(expected = IOException.class) + public void testWriteCompressWhenUncompressed() throws IOException { + Blob uncompressedLayerBlob = Blobs.from("uncompressedLayerBlob"); + // The detection of compression algorithm will fail + cacheStorageWriter.writeCompressed(uncompressedLayerBlob); } @Test @@ -117,7 +157,7 @@ public void testWriteUncompressed() throws IOException { CachedLayer cachedLayer = cacheStorageWriter.writeUncompressed(uncompressedLayerBlob, selector); - verifyCachedLayer(cachedLayer, uncompressedLayerBlob); + verifyCachedLayer(cachedLayer, uncompressedLayerBlob, compress(uncompressedLayerBlob)); // Verifies that the files are present. Path selectorFile = cacheStorageFiles.getSelectorFile(selector); @@ -291,7 +331,7 @@ public void testWriteMetadata_oci() MatcherAssert.assertThat( savedMetadata.getManifestList(), CoreMatchers.instanceOf(OciIndexTemplate.class)); - List savedManifestDescriptors = + List savedManifestDescriptors = ((OciIndexTemplate) savedMetadata.getManifestList()).getManifests(); Assert.assertEquals(1, savedManifestDescriptors.size()); @@ -354,9 +394,10 @@ public void testMoveIfDoesNotExist_exceptionAfterFailure() { assertThat(exception.getCause()).hasMessageThat().isEqualTo("foo"); } - private void verifyCachedLayer(CachedLayer cachedLayer, Blob uncompressedLayerBlob) + private void verifyCachedLayer( + CachedLayer cachedLayer, Blob uncompressedLayerBlob, Blob compressedLayerBlob) throws IOException { - BlobDescriptor layerBlobDescriptor = getDigest(compress(uncompressedLayerBlob)); + BlobDescriptor layerBlobDescriptor = getDigest(compressedLayerBlob); DescriptorDigest layerDiffId = getDigest(uncompressedLayerBlob).getDigest(); // Verifies cachedLayer is correct. diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/BuildContextTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/BuildContextTest.java index 98a365b013..4c052da192 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/BuildContextTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/BuildContextTest.java @@ -128,6 +128,7 @@ public void testBuilder() throws Exception { .setApplicationLayersCacheDirectory(expectedApplicationLayersCacheDirectory) .setBaseImageLayersCacheDirectory(expectedBaseImageLayersCacheDirectory) .setTargetFormat(ImageFormat.OCI) + .setEnablePlatformTags(true) .setAllowInsecureRegistries(true) .setLayerConfigurations(expectedLayerConfigurations) .setToolName(expectedCreatedBy) @@ -177,6 +178,7 @@ public void testBuilder() throws Exception { Assert.assertEquals(expectedCreatedBy, buildContext.getToolName()); Assert.assertEquals(expectedRegistryMirrors, buildContext.getRegistryMirrors()); Assert.assertNotNull(buildContext.getExecutorService()); + Assert.assertTrue(buildContext.getEnablePlatformTags()); } @Test @@ -220,6 +222,7 @@ public void testBuilder_default() throws CacheDirectoryCreationException { Assert.assertEquals(Collections.emptyList(), buildContext.getLayerConfigurations()); Assert.assertEquals("jib", buildContext.getToolName()); Assert.assertEquals(0, buildContext.getRegistryMirrors().size()); + Assert.assertFalse(buildContext.getEnablePlatformTags()); } @Test diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/ContainerConfigurationTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/ContainerConfigurationTest.java index 9939387c64..aeeb6ba49d 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/ContainerConfigurationTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/configuration/ContainerConfigurationTest.java @@ -106,7 +106,7 @@ public void testBuilder_nullValues() { ContainerConfiguration.builder().setEnvironment(nullValueMap); Assert.fail(); } catch (IllegalArgumentException ex) { - Assert.assertEquals("environment map contains null values", ex.getMessage()); + Assert.assertEquals("environment map contains null values for key(s): key", ex.getMessage()); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/AnotherDockerClient.java b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/AnotherDockerClient.java new file mode 100644 index 0000000000..64926306ec --- /dev/null +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/AnotherDockerClient.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.docker; + +import com.google.cloud.tools.jib.api.DockerClient; +import com.google.cloud.tools.jib.api.ImageDetails; +import com.google.cloud.tools.jib.api.ImageReference; +import com.google.cloud.tools.jib.image.ImageTarball; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Consumer; + +public class AnotherDockerClient implements DockerClient { + @Override + public boolean supported(Map parameters) { + return parameters.containsKey("test"); + } + + @Override + public String load(ImageTarball imageTarball, Consumer writtenByteCountListener) + throws InterruptedException, IOException { + return null; + } + + @Override + public void save( + ImageReference imageReference, Path outputPath, Consumer writtenByteCountListener) + throws InterruptedException, IOException {} + + @Override + public ImageDetails inspect(ImageReference imageReference) + throws IOException, InterruptedException { + return null; + } +} diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/DockerClientTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java similarity index 77% rename from jib-core/src/test/java/com/google/cloud/tools/jib/docker/DockerClientTest.java rename to jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java index 8f4c57b85d..a88539f8c0 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/DockerClientTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java @@ -16,18 +16,25 @@ package com.google.cloud.tools.jib.docker; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + import com.google.cloud.tools.jib.api.DescriptorDigest; +import com.google.cloud.tools.jib.api.DockerClient; +import com.google.cloud.tools.jib.api.DockerInfoDetails; import com.google.cloud.tools.jib.api.ImageReference; -import com.google.cloud.tools.jib.docker.DockerClient.DockerImageDetails; +import com.google.cloud.tools.jib.docker.CliDockerClient.DockerImageDetails; import com.google.cloud.tools.jib.image.ImageTarball; import com.google.cloud.tools.jib.json.JsonTemplateMapper; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; +import com.google.common.io.Resources; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.security.DigestException; @@ -47,9 +54,9 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.VoidAnswer1; -/** Tests for {@link DockerClient}. */ +/** Tests for {@link CliDockerClient}. */ @RunWith(MockitoJUnitRunner.class) -public class DockerClientTest { +public class CliDockerClientTest { @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -70,13 +77,74 @@ public void setUp() throws IOException { @Test public void testIsDockerInstalled_fail() { - Assert.assertFalse(DockerClient.isDockerInstalled(Paths.get("path/to/nonexistent/file"))); + Assert.assertFalse(CliDockerClient.isDockerInstalled(Paths.get("path/to/nonexistent/file"))); + } + + @Test + public void testIsDockerInstalled_pass() throws URISyntaxException { + Assert.assertTrue( + CliDockerClient.isDockerInstalled( + Paths.get(Resources.getResource("core/docker/emptyFile").toURI()))); + } + + @Test + public void testInfo() throws InterruptedException, IOException { + String dockerInfoJson = "{ \"OSType\": \"windows\"," + "\"Architecture\": \"arm64\"}"; + DockerClient testDockerClient = + new CliDockerClient( + subcommand -> { + assertThat(subcommand).containsExactly("info", "-f", "{{json .}}"); + return mockProcessBuilder; + }); + // Simulates stdout. + Mockito.when(mockProcess.getInputStream()) + .thenReturn(new ByteArrayInputStream(dockerInfoJson.getBytes())); + + DockerInfoDetails infoDetails = testDockerClient.info(); + assertThat(infoDetails.getArchitecture()).isEqualTo("arm64"); + assertThat(infoDetails.getOsType()).isEqualTo("windows"); + } + + @Test + public void testInfo_fail() throws InterruptedException { + DockerClient testDockerClient = + new CliDockerClient( + subcommand -> { + assertThat(subcommand).containsExactly("info", "-f", "{{json .}}"); + return mockProcessBuilder; + }); + Mockito.when(mockProcess.waitFor()).thenReturn(1); + Mockito.when(mockProcess.getErrorStream()) + .thenReturn(new ByteArrayInputStream("error".getBytes(StandardCharsets.UTF_8))); + + IOException exception = assertThrows(IOException.class, testDockerClient::info); + assertThat(exception) + .hasMessageThat() + .contains("'docker info' command failed with error: error"); + } + + @Test + public void testInfo_returnsUnknownKeys() throws InterruptedException, IOException { + String dockerInfoJson = "{ \"unknownOS\": \"windows\"," + "\"unknownArchitecture\": \"arm64\"}"; + DockerClient testDockerClient = + new CliDockerClient( + subcommand -> { + assertThat(subcommand).containsExactly("info", "-f", "{{json .}}"); + return mockProcessBuilder; + }); + Mockito.when(mockProcess.waitFor()).thenReturn(0); + Mockito.when(mockProcess.getInputStream()) + .thenReturn(new ByteArrayInputStream(dockerInfoJson.getBytes())); + + DockerInfoDetails infoDetails = testDockerClient.info(); + assertThat(infoDetails.getArchitecture()).isEmpty(); + assertThat(infoDetails.getOsType()).isEmpty(); } @Test public void testLoad() throws IOException, InterruptedException { DockerClient testDockerClient = - new DockerClient( + new CliDockerClient( subcommand -> { Assert.assertEquals(Collections.singletonList("load"), subcommand); return mockProcessBuilder; @@ -100,7 +168,7 @@ public void testLoad() throws IOException, InterruptedException { @Test public void testLoad_stdinFail() throws InterruptedException { - DockerClient testDockerClient = new DockerClient(ignored -> mockProcessBuilder); + DockerClient testDockerClient = new CliDockerClient(ignored -> mockProcessBuilder); Mockito.when(mockProcess.getOutputStream()) .thenReturn( @@ -125,7 +193,7 @@ public void write(int b) throws IOException { @Test public void testLoad_stdinFail_stderrFail() throws InterruptedException { - DockerClient testDockerClient = new DockerClient(ignored -> mockProcessBuilder); + DockerClient testDockerClient = new CliDockerClient(ignored -> mockProcessBuilder); Mockito.when(mockProcess.getOutputStream()) .thenReturn( @@ -157,7 +225,7 @@ public int read() throws IOException { @Test public void testLoad_stdoutFail() throws InterruptedException { - DockerClient testDockerClient = new DockerClient(ignored -> mockProcessBuilder); + DockerClient testDockerClient = new CliDockerClient(ignored -> mockProcessBuilder); Mockito.when(mockProcess.waitFor()).thenReturn(1); Mockito.when(mockProcess.getOutputStream()).thenReturn(ByteStreams.nullOutputStream()); @@ -213,7 +281,7 @@ public void testSave_fail() throws InterruptedException { @Test public void testDefaultProcessorBuilderFactory_customExecutable() { ProcessBuilder processBuilder = - DockerClient.defaultProcessBuilderFactory("docker-executable", ImmutableMap.of()) + CliDockerClient.defaultProcessBuilderFactory("docker-executable", ImmutableMap.of()) .apply(Arrays.asList("sub", "command")); Assert.assertEquals( @@ -229,7 +297,7 @@ public void testDefaultProcessorBuilderFactory_customEnvironment() { expectedEnvironment.putAll(environment); ProcessBuilder processBuilder = - DockerClient.defaultProcessBuilderFactory("docker", environment) + CliDockerClient.defaultProcessBuilderFactory("docker", environment) .apply(Collections.emptyList()); Assert.assertEquals(expectedEnvironment, processBuilder.environment()); @@ -238,7 +306,7 @@ public void testDefaultProcessorBuilderFactory_customEnvironment() { @Test public void testSize_fail() throws InterruptedException { DockerClient testDockerClient = - new DockerClient( + new CliDockerClient( subcommand -> { Assert.assertEquals("inspect", subcommand.get(0)); return mockProcessBuilder; @@ -317,7 +385,7 @@ public void testDockerImageDetails_emptyJson() throws IOException, DigestExcepti } private DockerClient makeDockerSaveClient() { - return new DockerClient( + return new CliDockerClient( subcommand -> { try { if (subcommand.contains("{{.Size}}")) { diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplateTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplateTest.java index 25051ac2f1..142522118e 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplateTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/json/DockerManifestEntryTemplateTest.java @@ -55,7 +55,7 @@ public void testToJson() throws URISyntaxException, IOException { @Test public void testFromJson() throws URISyntaxException, IOException { // Loads the expected JSON string. - Path jsonFile = Paths.get(Resources.getResource("core/json/loadmanifest.json").toURI()); + Path jsonFile = Paths.get(Resources.getResource("core/json/loadmanifest2.json").toURI()); String sourceJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8); DockerManifestEntryTemplate template = new ObjectMapper().readValue(sourceJson, DockerManifestEntryTemplate[].class)[0]; diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactoryTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactoryTest.java index b05fd9f529..9c05c08a05 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactoryTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/frontend/CredentialRetrieverFactoryTest.java @@ -31,6 +31,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import org.junit.Assert; @@ -57,7 +58,8 @@ public void setUp() throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException, IOException { Mockito.when( - mockDockerCredentialHelperFactory.create(Mockito.anyString(), Mockito.any(Path.class))) + mockDockerCredentialHelperFactory.create( + Mockito.anyString(), Mockito.any(Path.class), Mockito.anyMap())) .thenReturn(mockDockerCredentialHelper); Mockito.when(mockDockerCredentialHelper.retrieve()).thenReturn(FAKE_CREDENTIALS); Mockito.when(mockGoogleCredentials.getAccessToken()) @@ -76,7 +78,26 @@ public void testDockerCredentialHelper() throws CredentialRetrievalException { .retrieve()); Mockito.verify(mockDockerCredentialHelperFactory) - .create("registry", Paths.get("docker-credential-foo")); + .create("registry", Paths.get("docker-credential-foo"), Collections.emptyMap()); + Mockito.verify(mockLogger) + .accept( + LogEvent.lifecycle("Using credential helper docker-credential-foo for registry/repo")); + } + + @Test + public void testDockerCredentialHelperWithEnvironment() throws CredentialRetrievalException { + Map environment = Collections.singletonMap("ENV_VARIABLE", "Value"); + CredentialRetrieverFactory credentialRetrieverFactory = + createCredentialRetrieverFactory("registry", "repo", environment); + + Assert.assertEquals( + Optional.of(FAKE_CREDENTIALS), + credentialRetrieverFactory + .dockerCredentialHelper(Paths.get("docker-credential-foo")) + .retrieve()); + + Mockito.verify(mockDockerCredentialHelperFactory) + .create("registry", Paths.get("docker-credential-foo"), environment); Mockito.verify(mockLogger) .accept( LogEvent.lifecycle("Using credential helper docker-credential-foo for registry/repo")); @@ -92,7 +113,7 @@ public void testWellKnownCredentialHelpers() throws CredentialRetrievalException credentialRetrieverFactory.wellKnownCredentialHelpers().retrieve()); Mockito.verify(mockDockerCredentialHelperFactory) - .create("something.gcr.io", Paths.get("docker-credential-gcr")); + .create("something.gcr.io", Paths.get("docker-credential-gcr"), Collections.emptyMap()); Mockito.verify(mockLogger) .accept( LogEvent.lifecycle( @@ -115,7 +136,10 @@ public void testWellKnownCredentialHelpers_info() credentialRetrieverFactory.wellKnownCredentialHelpers().retrieve().isPresent()); Mockito.verify(mockDockerCredentialHelperFactory) - .create("something.amazonaws.com", Paths.get("docker-credential-ecr-login")); + .create( + "something.amazonaws.com", + Paths.get("docker-credential-ecr-login"), + Collections.emptyMap()); Mockito.verify(mockLogger).accept(LogEvent.info("warning")); Mockito.verify(mockLogger).accept(LogEvent.info(" Caused by: the root cause")); } @@ -164,7 +188,8 @@ public void testGoogleApplicationDefaultCredentials_adcNotPresent() mockDockerCredentialHelperFactory, () -> { throw new IOException("ADC not present"); - }); + }, + Collections.emptyMap()); Assert.assertFalse( credentialRetrieverFactory.googleApplicationDefaultCredentials().retrieve().isPresent()); @@ -264,8 +289,17 @@ public void testGoogleApplicationDefaultCredentials_serviceAccount() private CredentialRetrieverFactory createCredentialRetrieverFactory( String registry, String repository) { + return createCredentialRetrieverFactory(registry, repository, Collections.emptyMap()); + } + + private CredentialRetrieverFactory createCredentialRetrieverFactory( + String registry, String repository, Map environment) { ImageReference imageReference = ImageReference.of(registry, repository, null); return new CredentialRetrieverFactory( - imageReference, mockLogger, mockDockerCredentialHelperFactory, () -> mockGoogleCredentials); + imageReference, + mockLogger, + mockDockerCredentialHelperFactory, + () -> mockGoogleCredentials, + environment); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/http/TestWebServer.java b/jib-core/src/test/java/com/google/cloud/tools/jib/http/TestWebServer.java index eaa394400a..c5289dfe25 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/http/TestWebServer.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/http/TestWebServer.java @@ -23,7 +23,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URISyntaxException; @@ -91,8 +90,7 @@ public int getLocalPort() { } public String getEndpoint() { - String localhost = InetAddress.getLoopbackAddress().getHostAddress(); - return (https ? "https" : "http") + "://" + localhost + ":" + serverSocket.getLocalPort(); + return (https ? "https" : "http") + "://localhost:" + serverSocket.getLocalPort(); } @Override diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/OciIndexTemplateTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/OciIndexTemplateTest.java index 7f2724814e..f95a064306 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/OciIndexTemplateTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/OciIndexTemplateTest.java @@ -49,7 +49,8 @@ public void testToJson() throws DigestException, IOException, URISyntaxException "regis.try/repo:tag"); // Serializes the JSON object. - Assert.assertEquals(expectedJson, JsonTemplateMapper.toUtf8String(ociIndexJson)); + Assert.assertEquals( + expectedJson.replaceAll("[\r\n\t ]", ""), JsonTemplateMapper.toUtf8String(ociIndexJson)); } @Test @@ -63,6 +64,8 @@ public void testFromJson() throws IOException, URISyntaxException, DigestExcepti BuildableManifestTemplate.ContentDescriptorTemplate manifest = ociIndexJson.getManifests().get(0); + Assert.assertEquals(2, ociIndexJson.getSchemaVersion()); + Assert.assertEquals(OciIndexTemplate.MEDIA_TYPE, ociIndexJson.getManifestMediaType()); Assert.assertEquals( DescriptorDigest.fromDigest( "sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad"), @@ -71,4 +74,54 @@ public void testFromJson() throws IOException, URISyntaxException, DigestExcepti "regis.try/repo:tag", manifest.getAnnotations().get("org.opencontainers.image.ref.name")); Assert.assertEquals(1000, manifest.getSize()); } + + @Test + public void testToJsonWithPlatform() throws DigestException, IOException, URISyntaxException { + // Loads the expected JSON string. + Path jsonFile = Paths.get(Resources.getResource("core/json/ociindex_platforms.json").toURI()); + String expectedJson = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8); + + // Creates the JSON object to serialize. + OciIndexTemplate ociIndexJson = new OciIndexTemplate(); + + OciIndexTemplate.ManifestDescriptorTemplate ppc64leManifest = + new OciIndexTemplate.ManifestDescriptorTemplate( + OciManifestTemplate.MANIFEST_MEDIA_TYPE, + 7143, + DescriptorDigest.fromDigest( + "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f")); + ppc64leManifest.setPlatform("ppc64le", "linux"); + ociIndexJson.addManifest(ppc64leManifest); + + OciIndexTemplate.ManifestDescriptorTemplate amd64Manifest = + new OciIndexTemplate.ManifestDescriptorTemplate( + OciManifestTemplate.MANIFEST_MEDIA_TYPE, + 7682, + DescriptorDigest.fromDigest( + "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270")); + amd64Manifest.setPlatform("amd64", "linux"); + ociIndexJson.addManifest(amd64Manifest); + + // Serializes the JSON object. + Assert.assertEquals( + expectedJson.replaceAll("[\r\n\t ]", ""), JsonTemplateMapper.toUtf8String(ociIndexJson)); + } + + @Test + public void testFromJsonWithPlatform() throws IOException, URISyntaxException, DigestException { + // Loads the JSON string. + Path jsonFile = Paths.get(Resources.getResource("core/json/ociindex_platforms.json").toURI()); + + // Deserializes into a manifest JSON object. + OciIndexTemplate ociIndexJson = + JsonTemplateMapper.readJsonFromFile(jsonFile, OciIndexTemplate.class); + + Assert.assertEquals(2, ociIndexJson.getManifests().size()); + Assert.assertEquals( + "ppc64le", ociIndexJson.getManifests().get(0).getPlatform().getArchitecture()); + Assert.assertEquals("linux", ociIndexJson.getManifests().get(0).getPlatform().getOs()); + Assert.assertEquals( + "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + ociIndexJson.getDigestsForPlatform("ppc64le", "linux").get(0)); + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/DockerRegistryBearerTokenTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/DockerRegistryBearerTokenTest.java index eb75376a1e..06558ed94e 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/DockerRegistryBearerTokenTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/DockerRegistryBearerTokenTest.java @@ -16,10 +16,10 @@ package com.google.cloud.tools.jib.registry; -import com.google.api.client.util.Base64; import com.google.cloud.tools.jib.http.Authorization; import com.google.common.collect.Multimap; import java.nio.charset.StandardCharsets; +import java.util.Base64; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -53,8 +53,8 @@ public void testDecode_dockerToken() { @Test public void testDecode_nonToken() { String base64Text = - Base64.encodeBase64String( - "something other than a JWT token".getBytes(StandardCharsets.UTF_8)); + Base64.getEncoder() + .encodeToString("something other than a JWT token".getBytes(StandardCharsets.UTF_8)); Multimap decoded = RegistryClient.decodeTokenRepositoryGrants(base64Text); Assert.assertNull(decoded); } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPullerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPullerTest.java index 342eb6935c..e168ae0239 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPullerTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPullerTest.java @@ -104,6 +104,26 @@ public void testHandleResponse_v22() Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest()); } + @Test + public void testHandleResponse_ociManifest() + throws URISyntaxException, IOException, UnknownManifestFormatException { + Path ociManifestFile = Paths.get(Resources.getResource("core/json/ocimanifest.json").toURI()); + InputStream ociManifest = new ByteArrayInputStream(Files.readAllBytes(ociManifestFile)); + + DescriptorDigest expectedDigest = Digests.computeDigest(ociManifest).getDigest(); + ociManifest.reset(); + + Mockito.when(mockResponse.getBody()).thenReturn(ociManifest); + ManifestAndDigest manifestAndDigest = + new ManifestPuller<>( + fakeRegistryEndpointRequestProperties, "test-image-tag", OciManifestTemplate.class) + .handleResponse(mockResponse); + + MatcherAssert.assertThat( + manifestAndDigest.getManifest(), CoreMatchers.instanceOf(OciManifestTemplate.class)); + Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest()); + } + @Test public void testHandleResponse_v22ManifestListFailsWhenParsedAsV22Manifest() throws URISyntaxException, IOException, UnknownManifestFormatException { @@ -169,6 +189,28 @@ public void testHandleResponse_v22ManifestList() Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest()); } + @Test + public void testHandleResponse_OciIndex() + throws URISyntaxException, IOException, UnknownManifestFormatException { + Path ociIndexFile = + Paths.get(Resources.getResource("core/json/ociindex_platforms.json").toURI()); + InputStream ociIndex = new ByteArrayInputStream(Files.readAllBytes(ociIndexFile)); + + DescriptorDigest expectedDigest = Digests.computeDigest(ociIndex).getDigest(); + ociIndex.reset(); + + Mockito.when(mockResponse.getBody()).thenReturn(ociIndex); + ManifestAndDigest manifestAndDigest = + new ManifestPuller<>( + fakeRegistryEndpointRequestProperties, "test-image-tag", OciIndexTemplate.class) + .handleResponse(mockResponse); + OciIndexTemplate manifestTemplate = manifestAndDigest.getManifest(); + + MatcherAssert.assertThat(manifestTemplate, CoreMatchers.instanceOf(OciIndexTemplate.class)); + Assert.assertTrue(manifestTemplate.getManifests().size() > 0); + Assert.assertEquals(expectedDigest, manifestAndDigest.getDigest()); + } + @Test public void testHandleResponse_noSchemaVersion() throws IOException { Mockito.when(mockResponse.getBody()).thenReturn(stringToInputStreamUtf8("{}")); @@ -318,7 +360,8 @@ public void testGetAccept() { OciManifestTemplate.MANIFEST_MEDIA_TYPE, V22ManifestTemplate.MANIFEST_MEDIA_TYPE, V21ManifestTemplate.MEDIA_TYPE, - V22ManifestListTemplate.MANIFEST_MEDIA_TYPE), + V22ManifestListTemplate.MANIFEST_MEDIA_TYPE, + OciIndexTemplate.MEDIA_TYPE), testManifestPuller.getAccept()); Assert.assertEquals( @@ -343,5 +386,10 @@ public void testGetAccept() { "test-image-tag", V22ManifestListTemplate.class) .getAccept()); + Assert.assertEquals( + Collections.singletonList(OciIndexTemplate.MEDIA_TYPE), + new ManifestPuller<>( + fakeRegistryEndpointRequestProperties, "test-image-tag", OciIndexTemplate.class) + .getAccept()); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java index fad7c41fcf..0ce5be3dba 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java @@ -16,6 +16,9 @@ package com.google.cloud.tools.jib.registry; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException; import com.google.cloud.tools.jib.http.FailoverHttpClient; @@ -83,10 +86,11 @@ public void testFromAuthenticationMethod_bearer() "user-agent", httpClient) .get(); - Assert.assertEquals( - new URL("https://somerealm?service=someservice&scope=repository:someimage:scope"), - registryAuthenticator.getAuthenticationUrl( - null, Collections.singletonMap("someimage", "scope"))); + assertThat( + registryAuthenticator.getAuthenticationUrl( + null, Collections.singletonMap("someimage", "scope"))) + .isEqualTo( + new URL("https://somerealm?service=someservice&scope=repository:someimage:scope")); registryAuthenticator = RegistryAuthenticator.fromAuthenticationMethod( @@ -95,10 +99,11 @@ public void testFromAuthenticationMethod_bearer() "user-agent", httpClient) .get(); - Assert.assertEquals( - new URL("https://somerealm?service=someservice&scope=repository:someimage:scope"), - registryAuthenticator.getAuthenticationUrl( - null, Collections.singletonMap("someimage", "scope"))); + assertThat( + registryAuthenticator.getAuthenticationUrl( + null, Collections.singletonMap("someimage", "scope"))) + .isEqualTo( + new URL("https://somerealm?service=someservice&scope=repository:someimage:scope")); } @Test @@ -155,29 +160,34 @@ public void istAuthenticationUrl_oauth2() throws MalformedURLException { @Test public void testFromAuthenticationMethod_basic() throws RegistryAuthenticationFailedException { - Assert.assertFalse( - RegistryAuthenticator.fromAuthenticationMethod( + assertThat( + RegistryAuthenticator.fromAuthenticationMethod( + "Basic", registryEndpointRequestProperties, "user-agent", httpClient)) + .isEmpty(); + + assertThat( + RegistryAuthenticator.fromAuthenticationMethod( "Basic realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"", registryEndpointRequestProperties, "user-agent", - httpClient) - .isPresent()); + httpClient)) + .isEmpty(); - Assert.assertFalse( - RegistryAuthenticator.fromAuthenticationMethod( + assertThat( + RegistryAuthenticator.fromAuthenticationMethod( "BASIC realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"", registryEndpointRequestProperties, "user-agent", - httpClient) - .isPresent()); + httpClient)) + .isEmpty(); - Assert.assertFalse( - RegistryAuthenticator.fromAuthenticationMethod( + assertThat( + RegistryAuthenticator.fromAuthenticationMethod( "bASIC realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"", registryEndpointRequestProperties, "user-agent", - httpClient) - .isPresent()); + httpClient)) + .isEmpty(); } @Test diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigTest.java index 4c02238a49..3bd62ac404 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerConfigTest.java @@ -16,7 +16,6 @@ package com.google.cloud.tools.jib.registry.credentials; -import com.google.api.client.util.Base64; import com.google.cloud.tools.jib.json.JsonTemplateMapper; import com.google.cloud.tools.jib.registry.credentials.json.DockerConfigTemplate; import com.google.common.io.Resources; @@ -25,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; import org.junit.Assert; import org.junit.Test; @@ -32,7 +32,7 @@ public class DockerConfigTest { private static String decodeBase64(String base64String) { - return new String(Base64.decodeBase64(base64String), StandardCharsets.UTF_8); + return new String(Base64.getDecoder().decode(base64String), StandardCharsets.UTF_8); } @Test diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelperTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelperTest.java index 5e62e15fef..1f17aca613 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelperTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/credentials/DockerCredentialHelperTest.java @@ -18,13 +18,18 @@ import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.json.JsonTemplateMapper; +import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.function.Function; import org.junit.Assert; @@ -86,7 +91,7 @@ public void testRetrieve() Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder); DockerCredentialHelper credentialHelper = - new DockerCredentialHelper( + dockerCredentialHelper( "serverUrl", Paths.get("/foo/bar"), systemProperties, processBuilderFactory); Credential credential = credentialHelper.retrieve(); Assert.assertEquals("myusername", credential.getUsername()); @@ -95,6 +100,33 @@ public void testRetrieve() Mockito.verify(processBuilderFactory).apply(command); } + @Test + public void testRetrieveWithEnvironment() + throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException, + IOException { + List command = Arrays.asList(Paths.get("/foo/bar").toString(), "get"); + Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder); + Map processBuilderEnvironment = Mockito.spy(new HashMap<>()); + Mockito.when(processBuilder.environment()).thenReturn(processBuilderEnvironment); + + Map credHelperEnvironment = ImmutableMap.of("ENV_VARIABLE", "Value"); + DockerCredentialHelper credentialHelper = + dockerCredentialHelper( + "serverUrl", + Paths.get("/foo/bar"), + systemProperties, + processBuilderFactory, + credHelperEnvironment); + Credential credential = credentialHelper.retrieve(); + Assert.assertEquals("myusername", credential.getUsername()); + Assert.assertEquals("mysecret", credential.getPassword()); + + Mockito.verify(processBuilderFactory).apply(command); + Mockito.verify(processBuilderEnvironment).putAll(credHelperEnvironment); + Assert.assertEquals(1, processBuilderEnvironment.size()); + Assert.assertEquals("Value", processBuilderEnvironment.get("ENV_VARIABLE")); + } + @Test public void testRetrieve_cmdSuffixAddedOnWindows() throws CredentialHelperUnhandledServerUrlException, CredentialHelperNotFoundException, @@ -104,7 +136,7 @@ public void testRetrieve_cmdSuffixAddedOnWindows() Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder); DockerCredentialHelper credentialHelper = - new DockerCredentialHelper( + dockerCredentialHelper( "serverUrl", Paths.get("/foo/bar"), systemProperties, processBuilderFactory); Credential credential = credentialHelper.retrieve(); Assert.assertEquals("myusername", credential.getUsername()); @@ -122,7 +154,7 @@ public void testRetrieve_cmdSuffixAlreadyGivenOnWindows() Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder); DockerCredentialHelper credentialHelper = - new DockerCredentialHelper( + dockerCredentialHelper( "serverUrl", Paths.get("/foo/bar.CmD"), systemProperties, processBuilderFactory); Credential credential = credentialHelper.retrieve(); Assert.assertEquals("myusername", credential.getUsername()); @@ -140,7 +172,7 @@ public void testRetrieve_exeSuffixAlreadyGivenOnWindows() Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder); DockerCredentialHelper credentialHelper = - new DockerCredentialHelper( + dockerCredentialHelper( "serverUrl", Paths.get("/foo/bar.eXE"), systemProperties, processBuilderFactory); Credential credential = credentialHelper.retrieve(); Assert.assertEquals("myusername", credential.getUsername()); @@ -162,7 +194,7 @@ public void testRetrieve_cmdSuffixNotFoundOnWindows() Mockito.when(processBuilderFactory.apply(command)).thenReturn(processBuilder); DockerCredentialHelper credentialHelper = - new DockerCredentialHelper( + dockerCredentialHelper( "serverUrl", Paths.get("/foo/bar"), systemProperties, processBuilderFactory); Credential credential = credentialHelper.retrieve(); Assert.assertEquals("myusername", credential.getUsername()); @@ -183,7 +215,7 @@ public void testRetrieve_fileNotFoundExceptionMessage() "CreateProcess error=2, Das System kann die angegebene Datei nicht finden")); DockerCredentialHelper credentialHelper = - new DockerCredentialHelper( + dockerCredentialHelper( "serverUrl", Paths.get("/ignored"), systemProperties, processBuilderFactory); try { credentialHelper.retrieve(); @@ -192,4 +224,23 @@ public void testRetrieve_fileNotFoundExceptionMessage() Assert.assertNotNull(ex.getMessage()); } } + + private DockerCredentialHelper dockerCredentialHelper( + String serverUrl, + Path credentialHelper, + Properties properties, + Function, ProcessBuilder> processBuilderFactory) { + return dockerCredentialHelper( + serverUrl, credentialHelper, properties, processBuilderFactory, Collections.emptyMap()); + } + + private DockerCredentialHelper dockerCredentialHelper( + String serverUrl, + Path credentialHelper, + Properties properties, + Function, ProcessBuilder> processBuilderFactory, + Map environment) { + return new DockerCredentialHelper( + serverUrl, credentialHelper, properties, processBuilderFactory, environment); + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java index 9f82420b39..2bff23dbdc 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/tar/TarExtractorTest.java @@ -27,6 +27,7 @@ import java.nio.file.Paths; import java.nio.file.attribute.FileTime; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Assert; @@ -92,16 +93,31 @@ public void testExtract_modificationTimePreserved() throws URISyntaxException, I TarExtractor.extract(source, destination); - assertThat(Files.getLastModifiedTime(destination.resolve("file A"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:13:09Z"))); - assertThat(Files.getLastModifiedTime(destination.resolve("file B"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:12:00Z"))); - assertThat(Files.getLastModifiedTime(destination.resolve("folder"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:12:33Z"))); - assertThat(Files.getLastModifiedTime(destination.resolve("folder/nested folder"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:13:30Z"))); - assertThat(Files.getLastModifiedTime(destination.resolve("folder/nested folder/file C"))) - .isEqualTo(FileTime.from(Instant.parse("2019-08-01T16:12:21Z"))); + assertThat( + Files.getLastModifiedTime(destination.resolve("file A")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:13:09Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("file B")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:12:00Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("folder")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:12:33Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("folder/nested folder")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:13:30Z")); + assertThat( + Files.getLastModifiedTime(destination.resolve("folder/nested folder/file C")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2019-08-01T16:12:21Z")); } @Test @@ -113,14 +129,26 @@ public void testExtract_reproducibleTimestampsEnabled() throws URISyntaxExceptio TarExtractor.extract(source, destination, true); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1"))) - .isEqualTo(FileTime.fromMillis(1000L)); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2"))) - .isEqualTo(FileTime.fromMillis(1000L)); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3"))) - .isEqualTo(FileTime.fromMillis(1000L)); - assertThat(Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3/file.txt"))) - .isEqualTo(FileTime.from(Instant.parse("2021-01-29T21:10:02Z"))); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(FileTime.fromMillis(1000L).toInstant()); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1/level-2")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(FileTime.fromMillis(1000L).toInstant()); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(FileTime.fromMillis(1000L).toInstant()); + assertThat( + Files.getLastModifiedTime(destination.resolve("level-1/level-2/level-3/file.txt")) + .toInstant() + .truncatedTo(ChronoUnit.SECONDS)) + .isEqualTo(Instant.parse("2021-01-29T21:10:02Z")); } @Test diff --git a/jib-core/src/test/resources/META-INF/services/com.google.cloud.tools.jib.api.DockerClient b/jib-core/src/test/resources/META-INF/services/com.google.cloud.tools.jib.api.DockerClient new file mode 100644 index 0000000000..597997356e --- /dev/null +++ b/jib-core/src/test/resources/META-INF/services/com.google.cloud.tools.jib.api.DockerClient @@ -0,0 +1 @@ +com.google.cloud.tools.jib.docker.AnotherDockerClient \ No newline at end of file diff --git a/jib-core/src/test/resources/core/docker/emptyFile b/jib-core/src/test/resources/core/docker/emptyFile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/jib-core/src/test/resources/core/json/loadmanifest2.json b/jib-core/src/test/resources/core/json/loadmanifest2.json new file mode 100644 index 0000000000..1bbe7173f1 --- /dev/null +++ b/jib-core/src/test/resources/core/json/loadmanifest2.json @@ -0,0 +1 @@ +[{"Config":"config.json","RepoTags":["testregistry/testrepo:testtag"],"Layers":["layer1.tar.gz","layer2.tar.gz","layer3.tar.gz"],"LayerSources":{}}] diff --git a/jib-core/src/test/resources/core/json/ociindex.json b/jib-core/src/test/resources/core/json/ociindex.json index df9922663f..943384a6ef 100644 --- a/jib-core/src/test/resources/core/json/ociindex.json +++ b/jib-core/src/test/resources/core/json/ociindex.json @@ -1 +1,14 @@ -{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad","size":1000,"annotations":{"org.opencontainers.image.ref.name":"regis.try/repo:tag"}}]} \ No newline at end of file +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad", + "size": 1000, + "annotations": { + "org.opencontainers.image.ref.name": "regis.try/repo:tag" + } + } + ] +} \ No newline at end of file diff --git a/jib-core/src/test/resources/core/json/ociindex_platforms.json b/jib-core/src/test/resources/core/json/ociindex_platforms.json new file mode 100644 index 0000000000..d4254b474f --- /dev/null +++ b/jib-core/src/test/resources/core/json/ociindex_platforms.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + "size": 7143, + "platform": { + "architecture": "ppc64le", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", + "size": 7682, + "platform": { + "architecture": "amd64", + "os": "linux" + } + } + ] +} \ No newline at end of file diff --git a/jib-gradle-plugin/CHANGELOG.md b/jib-gradle-plugin/CHANGELOG.md index 837d2bc6d0..45d6741efd 100644 --- a/jib-gradle-plugin/CHANGELOG.md +++ b/jib-gradle-plugin/CHANGELOG.md @@ -3,13 +3,133 @@ All notable changes to this project will be documented in this file. ## [unreleased] +### Added + +### Changed + +### Fixed + +## 3.4.4 +- fix: allow pushing images with different arch/os to docker daemon [#4265](https://github.com/GoogleContainerTools/jib/issues/4265) +- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4267) + +## 3.4.3 + +### Fixed +- 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)) + +## 3.4.2 + +### Changed +- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) + +### Fixed +- fix: set PAX headers to address build reproducibility issue ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) +- 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)) + +## 3.4.1 + +### Fixed +- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171)) + +## 3.4.0 + ### Added +- feat: support gradle lazy configuration for entrypoint container parameter. ([#4003](https://github.com/GoogleContainerTools/jib/pull/4003)) ### Changed +- 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/)) +- deps: bump com.fasterxml.jackson:jackson-bom from 2.15.0 to 2.15.2. ([#4055](https://github.com/GoogleContainerTools/jib/pull/4055)) +- deps: bump com.google.guava:guava from 32.0.1-jre to 32.1.2-jre ([#4078](https://github.com/GoogleContainerTools/jib/pull/4078)) +- deps: bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9. ([#4098](https://github.com/GoogleContainerTools/jib/pull/4098)) + +### Fixed +- fix: fix WWW-Authenticate header parsing for Basic authentication ([#4035](https://github.com/GoogleContainerTools/jib/pull/4035/)) +- Fixed Gradle deprecations for Gradle 8.2. ([#3892](https://github.com/GoogleContainerTools/jib/pull/3892)) + +## 3.3.2 + +### Added +- Support lazy configuration for `jib.container.mainClass` and `jib.container.jvmFlags` parameters ([#3936](https://github.com/GoogleContainerTools/jib/pull/3936)) + +### Changed +- Log an info instead of warning when entrypoint makes the image to ignore jvm parameters ([#3904](https://github.com/GoogleContainerTools/jib/pull/3904)) + +Thanks to our community contributors @rmannibucau, @erdi! + +## 3.3.1 + +### Added +- 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)) + +### Changed +- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745)) + +### Fixed +- 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)) + +Thanks to our community contributors @creckord! + +## 3.3.0 + +### Added + +- 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)) +- Added lazy evaluation for `jib.extraDirectories` parameters using Gradle Property and Provider. ([#3737](https://github.com/GoogleContainerTools/jib/issues/3737)) +- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)). +- Support for OCI image index manifests ([#3715](https://github.com/GoogleContainerTools/jib/pull/3715)). +- Support for base image layer compressed with zstd ([#3717](https://github.com/GoogleContainerTools/jib/pull/3717)). + +### Changed + +- Upgraded slf4j-api to 2.0.0 ([#3735](https://github.com/GoogleContainerTools/jib/pull/3735)). +- Upgraded nullaway to 0.9.9 ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720)) +- 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)). +- 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)). + +Thanks to our community contributors @wwadge, @oliver-brm, @rquinio and @gsquared94! + +## 3.2.1 + +### Added + +- Environment variables can now be used in configuring credential helpers. ([#2814](https://github.com/GoogleContainerTools/jib/issues/2814)) + ```gradle + jib.to { + image = 'myimage' + credHelper { + helper = 'ecr-login' + environment = [ + AWS_PROFILE: 'profile' + ] + } + } + ``` + +### Changed +- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/pull/3612)). + +### Fixed + +- Fixed setting image format in Kotlin ([#3593](https://github.com/GoogleContainerTools/jib/pull/3593)). + +## 3.2.0 + +### Added + +- [`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)) +- 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)) + + +### Changed + +- 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)) +- Build will fail if `extraDirectories.paths` contain `from` directory that doesn't exist locally ([#3542](https://github.com/GoogleContainerTools/jib/issues/3542)) ### Fixed - 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)) +- 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)) ## 3.1.4 diff --git a/jib-gradle-plugin/README.md b/jib-gradle-plugin/README.md index b9470949f5..b18fba66e1 100644 --- a/jib-gradle-plugin/README.md +++ b/jib-gradle-plugin/README.md @@ -32,9 +32,11 @@ For information about the project, see the [Jib project README](../README.md). * [Example](#example) * [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) * [Authentication Methods](#authentication-methods) + * [Using Docker configuration files](#using-docker-configuration-files) * [Using Docker Credential Helpers](#using-docker-credential-helpers) * [Using Specific Credentials](#using-specific-credentials) * [Custom Container Entrypoint](#custom-container-entrypoint) + * [Reproducible Build Timestamps](#reproducible-build-timestamps) * [Jib Extensions](#jib-extensions) * [WAR Projects](#war-projects) * [Skaffold Integration](#skaffold-integration) @@ -51,7 +53,7 @@ In your Gradle Java project, add the plugin to your `build.gradle`: ```groovy plugins { - id 'com.google.cloud.tools.jib' version '3.1.4' + id 'com.google.cloud.tools.jib' version '3.4.4' } ``` @@ -107,7 +109,7 @@ For example, to build the image `my-docker-id/my-app`, the configuration would b jib.to.image = 'my-docker-id/my-app' ``` -#### Using [JFrog Container Registry (JCR)](https://www.jfrog.com/confluence/display/JFROG/JFrog+Container+Registry/) or [JFrog Artifactory](https://www.jfrog.com/confluence/display/JFROG/Getting+Started+with+Artifactory+as+a+Docker+Registry)... +#### 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)... *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.* @@ -182,7 +184,7 @@ Then, ```gradle build``` will build and containerize your application. ### Additional Build Artifacts -As 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.outputFiles.digest` and `jib.outputFiles.imageId` configuration properties. See [Extended Usage](#outputpaths-closure) for more details. +As 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. ## Multi Module Projects @@ -210,16 +212,16 @@ Field | Type | Default | Description Property | Type | Default | Description --- | --- | --- | --- -`image` | `String` | `adoptopenjdk:{8,11}-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). +`image` | `String` | `eclipse-temurin:{8,11,17,21}-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). `auth` | [`auth`](#auth-closure) | *None* | Specifies credentials directly (alternative to `credHelper`). `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-`). -`platforms` | [`platforms`](#platforms-closure) | See [`platforms`](#platforms-closure) | _Incubating feature_: Configures platforms of base images to select from a manifest list. +`platforms` | [`platforms`](#platforms-closure) | See [`platforms`](#platforms-closure) | Configures platforms of base images to select from a manifest list. `to` is a closure with the following properties: Property | Type | Default | Description --- | --- | --- | --- -`image` | `String` | *Required* | The image reference for the target image. This can also be specified via the `--image` command line option. +`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. `auth` | [`auth`](#auth-closure) | *None* | Specifies credentials directly (alternative to `credHelper`). `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-`). `tags` | `List` | *None* | Additional tags to push to. @@ -246,16 +248,16 @@ Property | Type | Default | Description --- | --- | --- | --- `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. `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. -`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`. -`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 `jib.container.entrypoint = 'INHERIT'` to indicate that the `entrypoint` and `args` should be inherited from the base image.\* +`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. +`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 `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. `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). `extraClasspath` | `List` | *None* | Additional paths in the container to prepend to the computed Java classpath. `expandClasspathDependencies` | `boolean` | `false` |

  • 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.
  • 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.
-`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`. +`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. `format` | `String` | `Docker` | Use `OCI` to build an [OCI container image](https://www.opencontainers.org/). -`jvmFlags` | `List` | *None* | Additional flags to pass into the JVM when running your application. +`jvmFlags` | `List` | *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. `labels` | `Map` | *None* | Key-value pairs for applying image metadata (similar to Docker's [LABEL](https://docs.docker.com/engine/reference/builder/#label) instruction). -`mainClass` | `String` | *Inferred*\*\* | The main class to launch your application from. +`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. `ports` | `List` | *None* | Ports that the container exposes at runtime (similar to Docker's [EXPOSE](https://docs.docker.com/engine/reference/builder/#expose) instruction). `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`. `volumes` | `List` | *None* | Specifies a list of mount points on the container. @@ -289,7 +291,7 @@ Property | Type | Default | Description Property | Type | Default | Description --- | --- | --- | --- -`executable` | `File` | `docker` | Sets the path to the Docker executable that is called to load the image into the Docker daemon. +`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. `environment` | `Map` | *None* | Sets environment variables used by the Docker executable. #### System Properties @@ -312,7 +314,7 @@ The following table contains additional system properties that are not available Property | Type | Default | Description --- | --- | --- | --- `jib.httpTimeout` | `int` | `20000` | HTTP connection/read timeout for registry interactions, in milliseconds. Use a value of `0` for an infinite timeout. -`jib.useOnlyProjectCache` | `boolean` | `false` | If set to true, Jib does not share a cache between different Maven projects. +`jib.useOnlyProjectCache` | `boolean` | `false` | If set to true, Jib does not share a cache between different Gradle projects. `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. `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. `jib.console` | `String` | *None* | If set to `plain`, Jib will print plaintext log messages rather than display a progress bar during the build. @@ -324,7 +326,7 @@ Property | Type | Default | Description *\*\*\* The default base image cache is in the following locations on each platform:* * *Linux: `[cache root]/google-cloud-tools-java/jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/.cache/` if not set)* * *Mac: `[cache root]/Google/Jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/Library/Caches/` if not set)* - * *Windows: `[cache root]\Google\Jib\Cache`, where `[cache root]` is `$XDG_CACHE_HOME` (`%LOCALAPPDATA%` if not set)* + * *Windows: `[cache root]\Google\Jib\Cache`, where `[cache root]` is `%XDG_CACHE_HOME%` (`%LOCALAPPDATA%` if not set)* ### Global Jib Configuration @@ -332,12 +334,12 @@ Some options can be set in the global Jib configuration file. The file is at the * *Linux: `[config root]/google-cloud-tools-java/jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/.config/` if not set)* * *Mac: `[config root]/Google/Jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/Library/Preferences/Config/` if not set)* -* *Windows: `[config root]\Google\Jib\Config\config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`%LOCALAPPDATA%` if not set)* +* *Windows: `[config root]\Google\Jib\Config\config.json`, where `[config root]` is `%XDG_CONFIG_HOME%` (`%LOCALAPPDATA%` if not set)* -#### Properties +#### Properties * `disableUpdateCheck`: when set to true, disables the periodic up-to-date version check. -* `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 successfuly pull a base image. +* `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. ```json { @@ -393,8 +395,8 @@ There are three different types of base images that Jib accepts: an image from a Prefix | Example | Type --- | --- | --- -*None* | `adoptopenjdk:11-jre` | Pulls the base image from a registry. -`registry://` | `registry://adoptopenjdk:11-jre` | Pulls the base image from a registry. +*None* | `openjdk:11-jre` | Pulls the base image from a registry. +`registry://` | `registry://eclipse-temurin:11-jre` | Pulls the base image from a registry. `docker://` | `docker://busybox` | Retrieves the base image from the Docker daemon. `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`). @@ -458,9 +460,37 @@ Using `paths` as a closure, you may also specify the target of the copy and incl } ``` +You 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`: + +```groovy +extraDirectories { + paths = project.provider { 'src/main/custom-extra-dir' } + permissions = project.provider { ['/path/on/container/to/fileA': '755'] } +} +``` + +```groovy +extraDirectories { + paths { + path { + from = project.provider { 'src/main/custom-extra-dir' } + into = project.provider { '/dest-in-container' } + includes = project.provider { ['*.txt', '**/*.txt'] } + excludes = project.provider { ['hidden.txt'] } + } + } +} +``` + ### Authentication Methods -Pushing/pulling from private registries require authorization credentials. These can be [retrieved using Docker credential helpers](#using-docker-credential-helpers). If you do not define credentials explicitly, Jib will try to [use credentials defined in your Docker config](/../../issues/101) or infer common credential helpers. +Pushing/pulling from private registries require authorization credentials. + +#### Using Docker configuration files + +* 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`. + +See [this issue](/../../issues/101) and [`man containers-auth.json`](https://www.mankier.com/5/containers-auth.json) for more information about the files. #### Using Docker Credential Helpers @@ -543,6 +573,21 @@ Therefore, *for example*, the following commands will be able to launch your app - (Java 9+) `java -cp @/app/jib-classpath-file @/app/jib-main-class-file` - (with shell) `java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )` +### Reproducible Build Timestamps + +To 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. + +Another, 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`: + +```groovy +jib { + container { + creationTime = project.provider { project.ext.git['git.commit.time'] } + } +} +``` + +This would build an image with the creation time set to the time of the latest commit from `project.ext.git['git.commit.time']`. ### Jib Extensions diff --git a/jib-gradle-plugin/build.gradle b/jib-gradle-plugin/build.gradle index d7bc77871e..46f416ab1e 100644 --- a/jib-gradle-plugin/build.gradle +++ b/jib-gradle-plugin/build.gradle @@ -10,7 +10,7 @@ plugins { // to install for local testing - do not load this plugin when publishing to plugin portal // 'maven-publish' and 'com.gradle.plugin-publish' do not interact well with each other // https://discuss.gradle.org/t/debug-an-issue-in-publish-plugin-gradle-plugin-not-being-prepended-to-groupid/32720 -if (version.contains("SNAPSHOT")) { +if (version.contains('SNAPSHOT')) { apply plugin: 'maven-publish' publishing { @@ -78,7 +78,7 @@ gradlePlugin { } } } -tasks.publishPlugins.dependsOn integrationTest +tasks.publishPlugins.dependsOn build /* RELEASE */ /* ECLIPSE */ diff --git a/jib-gradle-plugin/gradle.properties b/jib-gradle-plugin/gradle.properties index 3e072365df..f78665a194 100644 --- a/jib-gradle-plugin/gradle.properties +++ b/jib-gradle-plugin/gradle.properties @@ -1 +1 @@ -version = 3.1.5-SNAPSHOT +version = 3.4.5-SNAPSHOT diff --git a/jib-gradle-plugin/scripts/release.sh b/jib-gradle-plugin/scripts/release.sh index 9f984ebaf8..faa3ed076a 100755 --- a/jib-gradle-plugin/scripts/release.sh +++ b/jib-gradle-plugin/scripts/release.sh @@ -7,19 +7,13 @@ readonly PUBLISH_SECRET=$(cat "${KOKORO_KEYSTORE_DIR}/72743_gradle_publish_secre set -o xtrace -gcloud components install docker-credential-gcr - -# docker-credential-gcr uses GOOGLE_APPLICATION_CREDENTIALS as the credentials key file -export readonly GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_KEYSTORE_DIR}/72743_jib_integration_testing_key" -docker-credential-gcr configure-docker +# From default hostname, get id of container to exclude +CONTAINER_ID=$(hostname) +echo "$CONTAINER_ID" # Stops any left-over containers. -docker stop $(docker ps --all --quiet) || true -docker kill $(docker ps --all --quiet) || true - -# Sets the integration testing project. -export readonly JIB_INTEGRATION_TESTING_PROJECT=jib-integration-testing - +docker stop $(docker ps --all --quiet | grep -v "$CONTAINER_ID") || true +docker kill $(docker ps --all --quiet | grep -v "$CONTAINER_ID") || true cd github/jib echo "gradle publish" diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/DefaultTargetProjectIntegrationTest.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/DefaultTargetProjectIntegrationTest.java index 5c14fae5c9..8d78986bc9 100644 --- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/DefaultTargetProjectIntegrationTest.java +++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/DefaultTargetProjectIntegrationTest.java @@ -41,23 +41,19 @@ public class DefaultTargetProjectIntegrationTest { */ private static void assertDockerInspect(String imageReference) throws IOException, InterruptedException { - String dockerInspect = new Command("docker", "inspect", imageReference).run(); + String dockerInspectExposedPorts = + new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference) + .run(); + String dockerInspectLabels = + new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run(); + MatcherAssert.assertThat( - dockerInspect, + dockerInspectExposedPorts, CoreMatchers.containsString( - " \"ExposedPorts\": {\n" - + " \"1000/tcp\": {},\n" - + " \"2000/udp\": {},\n" - + " \"2001/udp\": {},\n" - + " \"2002/udp\": {},\n" - + " \"2003/udp\": {}")); + "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}")); MatcherAssert.assertThat( - dockerInspect, - CoreMatchers.containsString( - " \"Labels\": {\n" - + " \"key1\": \"value1\",\n" - + " \"key2\": \"value2\"\n" - + " }")); + dockerInspectLabels, + CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\"")); } @Test diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/EmptyProjectIntegrationTest.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/EmptyProjectIntegrationTest.java index d14711d83d..b639885605 100644 --- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/EmptyProjectIntegrationTest.java +++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/EmptyProjectIntegrationTest.java @@ -44,23 +44,18 @@ public class EmptyProjectIntegrationTest { */ private static void assertDockerInspect(String imageReference) throws IOException, InterruptedException { - String dockerInspect = new Command("docker", "inspect", imageReference).run(); + String dockerInspectExposedPorts = + new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference) + .run(); + String dockerInspectLabels = + new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run(); MatcherAssert.assertThat( - dockerInspect, + dockerInspectExposedPorts, CoreMatchers.containsString( - " \"ExposedPorts\": {\n" - + " \"1000/tcp\": {},\n" - + " \"2000/udp\": {},\n" - + " \"2001/udp\": {},\n" - + " \"2002/udp\": {},\n" - + " \"2003/udp\": {}")); + "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}")); MatcherAssert.assertThat( - dockerInspect, - CoreMatchers.containsString( - " \"Labels\": {\n" - + " \"key1\": \"value1\",\n" - + " \"key2\": \"value2\"\n" - + " }")); + dockerInspectLabels, + CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\"")); } @Test diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/JibRunHelper.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/JibRunHelper.java index c48b88fa66..70119236ed 100644 --- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/JibRunHelper.java +++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/JibRunHelper.java @@ -22,11 +22,7 @@ import com.google.cloud.tools.jib.api.DescriptorDigest; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; -import com.google.cloud.tools.jib.blob.Blobs; import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -35,7 +31,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import javax.annotation.Nullable; import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.TaskOutcome; @@ -46,24 +41,6 @@ /** Helper class to run integration tests. */ public class JibRunHelper { - @Nullable - static String getContent(URL url) throws InterruptedException { - for (int i = 0; i < 40; i++) { - Thread.sleep(500); - try { - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { - try (InputStream in = connection.getInputStream()) { - return Blobs.writeToString(Blobs.from(in)); - } - } - } catch (IOException ignored) { - // ignored - } - } - return null; - } - static String buildAndRun(TestProject testProject, String imageReference) throws IOException, InterruptedException, DigestException { return buildAndRun(testProject, imageReference, "build.gradle"); diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java index 11ef284a65..faf32e883e 100644 --- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java +++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java @@ -17,6 +17,9 @@ package com.google.cloud.tools.jib.gradle; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import com.google.cloud.tools.jib.Command; import com.google.cloud.tools.jib.IntegrationTestingConfiguration; @@ -25,6 +28,7 @@ import com.google.cloud.tools.jib.api.InvalidImageReferenceException; import com.google.cloud.tools.jib.registry.LocalRegistry; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -35,8 +39,6 @@ import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; -import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; @@ -52,15 +54,18 @@ public class SingleProjectIntegrationTest { @ClassRule public static final LocalRegistry localRegistry2 = - new LocalRegistry(6000, "testuser2", "testpassword2"); + new LocalRegistry(6000, "testuser", "testpassword"); @ClassRule public static final TestProject simpleTestProject = new TestProject("simple"); @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - private static boolean isJava11RuntimeOrHigher() { + private final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; + + private static boolean isJavaRuntimeAtLeast(int version) { Iterable split = Splitter.on(".").split(System.getProperty("java.version")); - return Integer.valueOf(split.iterator().next()) >= 11; + return Integer.valueOf(split.iterator().next()) >= version; } private static String getWorkingDirectory(String imageReference) @@ -93,31 +98,27 @@ private static int getLayerSize(String imageReference) throws IOException, Inter */ private static void assertDockerInspect(String imageReference) throws IOException, InterruptedException { - String dockerInspect = new Command("docker", "inspect", imageReference).run(); - assertThat(dockerInspect) - .contains( - " \"Volumes\": {\n" - + " \"/var/log\": {},\n" - + " \"/var/log2\": {}\n" - + " },"); - assertThat(dockerInspect) - .contains( - " \"ExposedPorts\": {\n" - + " \"1000/tcp\": {},\n" - + " \"2000/udp\": {},\n" - + " \"2001/udp\": {},\n" - + " \"2002/udp\": {},\n" - + " \"2003/udp\": {}"); - assertThat(dockerInspect) - .contains( - " \"Labels\": {\n" - + " \"key1\": \"value1\",\n" - + " \"key2\": \"value2\"\n" - + " }"); + String dockerInspectVolumes = + new Command("docker", "inspect", "-f", "'{{json .Config.Volumes}}'", imageReference).run(); + String dockerInspectExposedPorts = + new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference) + .run(); + String dockerInspectLabels = + new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run(); + + MatcherAssert.assertThat( + dockerInspectVolumes, CoreMatchers.containsString("\"/var/log\":{},\"/var/log2\":{}")); + MatcherAssert.assertThat( + dockerInspectExposedPorts, + CoreMatchers.containsString( + "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}")); + MatcherAssert.assertThat( + dockerInspectLabels, + CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\"")); } private static String readDigestFile(Path digestPath) throws IOException, DigestException { - Assert.assertTrue(Files.exists(digestPath)); + assertThat(Files.exists(digestPath)).isTrue(); String digest = new String(Files.readAllBytes(digestPath), StandardCharsets.UTF_8); return DescriptorDigest.fromDigest(digest).toString(); } @@ -139,19 +140,28 @@ private static String buildAndRunComplex( "-b=complex-build.gradle"); JibRunHelper.assertBuildSuccess(buildResult, "jib", "Built and pushed image as "); - MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(imageReference)); + assertThat(buildResult.getOutput()).contains(imageReference); targetRegistry.pull(imageReference); assertDockerInspect(imageReference); String history = new Command("docker", "history", imageReference).run(); - MatcherAssert.assertThat(history, CoreMatchers.containsString("jib-gradle-plugin")); + assertThat(history).contains("jib-gradle-plugin"); String output = new Command("docker", "run", "--rm", imageReference).run(); - Assert.assertEquals( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n" - + "-Xms512m\n-Xdebug\nenvvalue1\nenvvalue2\n", - output); + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. An argument.", + "1970-01-01T00:00:01Z", + "rwxr-xr-x", + "rwxrwxrwx", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z", + "-Xms512m", + "-Xdebug", + "envvalue1", + "envvalue2"); return output; } @@ -170,39 +180,42 @@ public void testBuild_simple() + System.nanoTime(); // Test empty output error - try { - simpleTestProject.build( - "clean", - "jib", - "-Djib.useOnlyProjectCache=true", - "-Djib.console=plain", - "-x=classes", - "-D_TARGET_IMAGE=" + targetImage); - Assert.fail(); - - } catch (UnexpectedBuildFailure ex) { - MatcherAssert.assertThat( - ex.getMessage(), - CoreMatchers.containsString( - "No classes files were found - did you compile your project?")); - } + Exception exception = + assertThrows( + UnexpectedBuildFailure.class, + () -> + simpleTestProject.build( + "clean", + "jib", + "-Djib.useOnlyProjectCache=true", + "-Djib.console=plain", + "-x=classes", + "-D_TARGET_IMAGE=" + targetImage)); + assertThat(exception) + .hasMessageThat() + .contains("No classes files were found - did you compile your project?"); String output = JibRunHelper.buildAndRun(simpleTestProject, targetImage); - - Assert.assertEquals( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", - output); + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. An argument.", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); String digest = readDigestFile(simpleTestProject.getProjectRoot().resolve("build/jib-image.digest")); String imageReferenceWithDigest = ImageReference.parse(targetImage).withQualifier(digest).toString(); - Assert.assertEquals(output, JibRunHelper.pullAndRunBuiltImage(imageReferenceWithDigest)); + assertThat(JibRunHelper.pullAndRunBuiltImage(imageReferenceWithDigest)).isEqualTo(output); String id = readDigestFile(simpleTestProject.getProjectRoot().resolve("build/jib-image.id")); - Assert.assertNotEquals(digest, id); - Assert.assertEquals(output, new Command("docker", "run", "--rm", id).run()); + assertThat(id).isNotEqualTo(digest); + assertThat(new Command("docker", "run", "--rm", id).run()).isEqualTo(output); assertDockerInspect(targetImage); assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH); @@ -220,11 +233,20 @@ public void testBuild_dockerDaemonBase() IntegrationTestingConfiguration.getTestRepositoryLocation() + "/simplewithdockerdaemonbase:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", + String output = JibRunHelper.buildAndRunFromLocalBase( - targetImage, "docker://gcr.io/distroless/java:latest")); + targetImage, "docker://gcr.io/distroless/java:latest"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. An argument.", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); } @Test @@ -235,10 +257,18 @@ public void testBuild_tarBase() throws IOException, InterruptedException, Digest IntegrationTestingConfiguration.getTestRepositoryLocation() + "/simplewithtarbase:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", - JibRunHelper.buildAndRunFromLocalBase(targetImage, "tar://" + path)); + String output = JibRunHelper.buildAndRunFromLocalBase(targetImage, "tar://" + path); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. An argument.", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); } @Test @@ -248,64 +278,86 @@ public void testBuild_failOffline() { + "/simpleimageoffline:gradle" + System.nanoTime(); - try { - simpleTestProject.build( - "--offline", - "clean", - "jib", - "-Djib.useOnlyProjectCache=true", - "-Djib.console=plain", - "-D_TARGET_IMAGE=" + targetImage); - Assert.fail(); - } catch (UnexpectedBuildFailure ex) { - MatcherAssert.assertThat( - ex.getMessage(), - CoreMatchers.containsString("Cannot build to a container registry in offline mode")); - } + Exception exception = + assertThrows( + UnexpectedBuildFailure.class, + () -> + simpleTestProject.build( + "--offline", + "clean", + "jib", + "-Djib.useOnlyProjectCache=true", + "-Djib.console=plain", + "-D_TARGET_IMAGE=" + targetImage)); + assertThat(exception) + .hasMessageThat() + .contains("Cannot build to a container registry in offline mode"); } @Test - public void testDockerDaemon_simpleOnJava11() + public void testDockerDaemon_simpleOnJava17() throws DigestException, IOException, InterruptedException { - Assume.assumeTrue(isJava11RuntimeOrHigher()); + assumeTrue(isJavaRuntimeAtLeast(17)); String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. \n1970-01-01T00:00:01Z\n", + String output = JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, targetImage, "build-java11.gradle")); + simpleTestProject, targetImage, "build-java17.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly("Hello, world. ", "1970-01-01T00:00:01Z"); } @Test - public void testDockerDaemon_simpleWithIncompatibleJava11() + public void testDockerDaemon_simpleOnJava11() throws DigestException, IOException, InterruptedException { - Assume.assumeTrue(isJava11RuntimeOrHigher()); - - try { - JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, "willnotbuild", "build-java11-incompatible.gradle"); - Assert.fail(); - - } catch (UnexpectedBuildFailure ex) { - MatcherAssert.assertThat( - ex.getMessage(), - CoreMatchers.containsString( - "Your project is using Java 11 but the base image is for Java 8, perhaps you should " - + "configure a Java 11-compatible base image using the 'jib.from.image' " - + "parameter, or set targetCompatibility = 8 or below in your build " - + "configuration")); - } + assumeTrue(isJavaRuntimeAtLeast(11)); + + String targetImage = "simpleimage:gradle" + System.nanoTime(); + String output = + JibRunHelper.buildToDockerDaemonAndRun( + simpleTestProject, targetImage, "build-java11.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly("Hello, world. ", "1970-01-01T00:00:01Z"); + } + + @Test + public void testDockerDaemon_simpleWithIncompatibleJava11() { + assumeTrue(isJavaRuntimeAtLeast(11)); + + Exception exception = + assertThrows( + UnexpectedBuildFailure.class, + () -> + JibRunHelper.buildToDockerDaemonAndRun( + simpleTestProject, "willnotbuild", "build-java11-incompatible.gradle")); + assertThat(exception) + .hasMessageThat() + .contains( + "Your project is using Java 11 but the base image is for Java 8, perhaps you should " + + "configure a Java 11-compatible base image using the 'jib.from.image' parameter, " + + "or set targetCompatibility = 8 or below in your build configuration"); } @Test public void testDockerDaemon_simple_multipleExtraDirectories() throws DigestException, IOException, InterruptedException { String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. \n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", + String output = JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, targetImage, "build-extra-dirs.gradle")); + simpleTestProject, targetImage, "build-extra-dirs.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. ", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual } @@ -313,11 +365,20 @@ public void testDockerDaemon_simple_multipleExtraDirectories() public void testDockerDaemon_simple_multipleExtraDirectoriesWithAlternativeConfig() throws DigestException, IOException, InterruptedException { String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. \n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", + String output = JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, targetImage, "build-extra-dirs2.gradle")); + simpleTestProject, targetImage, "build-extra-dirs2.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. ", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual } @@ -325,11 +386,22 @@ public void testDockerDaemon_simple_multipleExtraDirectoriesWithAlternativeConfi public void testDockerDaemon_simple_multipleExtraDirectoriesWithClosure() throws DigestException, IOException, InterruptedException { String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. \n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\nbaz\n1970-01-01T00:00:01Z\n", + String output = JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, targetImage, "build-extra-dirs3.gradle")); + simpleTestProject, targetImage, "build-extra-dirs3.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. ", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z", + "baz", + "1970-01-01T00:00:01Z"); assertThat(getLayerSize(targetImage)).isEqualTo(11); // one more than usual } @@ -353,9 +425,9 @@ public void testDockerDaemon_simple_extraDirectoriesFiltering() @Test public void testBuild_complex() throws IOException, InterruptedException, DigestException, InvalidImageReferenceException { - String targetImage = "localhost:6000/compleximage:gradle" + System.nanoTime(); + String targetImage = dockerHost + ":6000/compleximage:gradle" + System.nanoTime(); Instant beforeBuild = Instant.now(); - String output = buildAndRunComplex(targetImage, "testuser2", "testpassword2", localRegistry2); + String output = buildAndRunComplex(targetImage, "testuser", "testpassword", localRegistry2); String digest = readDigestFile( @@ -363,13 +435,13 @@ public void testBuild_complex() String imageReferenceWithDigest = ImageReference.parse(targetImage).withQualifier(digest).toString(); localRegistry2.pull(imageReferenceWithDigest); - Assert.assertEquals( - output, new Command("docker", "run", "--rm", imageReferenceWithDigest).run()); + assertThat(new Command("docker", "run", "--rm", imageReferenceWithDigest).run()) + .isEqualTo(output); String id = readDigestFile(simpleTestProject.getProjectRoot().resolve("different-jib-image.id")); - Assert.assertNotEquals(digest, id); - Assert.assertEquals(output, new Command("docker", "run", "--rm", id).run()); + assertThat(id).isNotEqualTo(digest); + assertThat(new Command("docker", "run", "--rm", id).run()).isEqualTo(output); assertThat(JibRunHelper.getCreationTime(targetImage)).isGreaterThan(beforeBuild); assertThat(getWorkingDirectory(targetImage)).isEqualTo("/"); @@ -377,7 +449,7 @@ public void testBuild_complex() @Test public void testBuild_complex_sameFromAndToRegistry() throws IOException, InterruptedException { - String targetImage = "localhost:5000/compleximage:gradle" + System.nanoTime(); + String targetImage = dockerHost + ":5000/compleximage:gradle" + System.nanoTime(); Instant beforeBuild = Instant.now(); buildAndRunComplex(targetImage, "testuser", "testpassword", localRegistry1); assertThat(JibRunHelper.getCreationTime(targetImage)).isGreaterThan(beforeBuild); @@ -387,10 +459,19 @@ public void testBuild_complex_sameFromAndToRegistry() throws IOException, Interr @Test public void testDockerDaemon_simple() throws IOException, InterruptedException, DigestException { String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", - JibRunHelper.buildToDockerDaemonAndRun(simpleTestProject, targetImage, "build.gradle")); + String output = + JibRunHelper.buildToDockerDaemonAndRun(simpleTestProject, targetImage, "build.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. An argument.", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH); assertDockerInspect(targetImage); assertThat(getWorkingDirectory(targetImage)).isEqualTo("/home"); @@ -400,43 +481,47 @@ public void testDockerDaemon_simple() throws IOException, InterruptedException, public void testDockerDaemon_jarContainerization() throws DigestException, IOException, InterruptedException { String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. \nImplementation-Title: helloworld\nImplementation-Version: 1\n", + String output = JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, targetImage, "build-jar-containerization.gradle")); + simpleTestProject, targetImage, "build-jar-containerization.gradle"); + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. ", "Implementation-Title: helloworld", "Implementation-Version: 1"); } @Test public void testBuild_skipDownloadingBaseImageLayers() throws IOException, InterruptedException { Path baseLayersCacheDirectory = simpleTestProject.getProjectRoot().resolve("build/jib-base-cache/layers"); - String targetImage = "localhost:6000/simpleimage:gradle" + System.nanoTime(); + String targetImage = dockerHost + ":6000/simpleimage:gradle" + System.nanoTime(); - buildAndRunComplex(targetImage, "testuser2", "testpassword2", localRegistry2); + buildAndRunComplex(targetImage, "testuser", "testpassword", localRegistry2); // Base image layer tarballs exist. - Assert.assertTrue(Files.exists(baseLayersCacheDirectory)); - Assert.assertTrue(baseLayersCacheDirectory.toFile().list().length >= 2); + assertThat(Files.exists(baseLayersCacheDirectory)).isTrue(); + assertThat(baseLayersCacheDirectory.toFile().list().length >= 2).isTrue(); - buildAndRunComplex(targetImage, "testuser2", "testpassword2", localRegistry2); + buildAndRunComplex(targetImage, "testuser", "testpassword", localRegistry2); // no base layers downloaded after "gradle clean jib ..." - Assert.assertFalse(Files.exists(baseLayersCacheDirectory)); + assertThat(Files.exists(baseLayersCacheDirectory)).isFalse(); } @Test public void testDockerDaemon_timestampCustom() throws DigestException, IOException, InterruptedException { String targetImage = "simpleimage:gradle" + System.nanoTime(); - Assert.assertEquals( - "Hello, world. \n2011-12-03T01:15:30Z\n", + String output = JibRunHelper.buildToDockerDaemonAndRun( - simpleTestProject, targetImage, "build-timestamps-custom.gradle")); + simpleTestProject, targetImage, "build-timestamps-custom.gradle"); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly("Hello, world. ", "2011-12-03T01:15:30Z"); assertThat(JibRunHelper.getCreationTime(targetImage)) .isEqualTo(Instant.parse("2013-11-04T21:29:30Z")); } @Test public void testBuild_dockerClient() throws IOException, InterruptedException, DigestException { - Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows")); + assumeFalse(System.getProperty("os.name").startsWith("Windows")); new Command( "chmod", "+x", simpleTestProject.getProjectRoot().resolve("mock-docker.sh").toString()) .run(); @@ -455,9 +540,8 @@ public void testBuild_dockerClient() throws IOException, InterruptedException, D JibRunHelper.assertBuildSuccess( buildResult, "jibDockerBuild", "Built image to Docker daemon as "); JibRunHelper.assertThatExpectedImageDigestAndIdReturned(simpleTestProject.getProjectRoot()); - MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(targetImage)); - MatcherAssert.assertThat( - buildResult.getOutput(), CoreMatchers.containsString("Docker load called. value1 value2")); + assertThat(buildResult.getOutput()).contains(targetImage); + assertThat(buildResult.getOutput()).contains("Docker load called. value1 value2"); } @Test @@ -479,15 +563,43 @@ public void testBuildTar_simple() throws IOException, InterruptedException { "-D_TARGET_IMAGE=" + targetImage); JibRunHelper.assertBuildSuccess(buildResult, "jibBuildTar", "Built image tarball at "); - MatcherAssert.assertThat(buildResult.getOutput(), CoreMatchers.containsString(outputPath)); + assertThat(buildResult.getOutput()).contains(outputPath); new Command("docker", "load", "--input", outputPath).run(); - Assert.assertEquals( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" - + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n", - new Command("docker", "run", "--rm", targetImage).run()); + String output = new Command("docker", "run", "--rm", targetImage).run(); + + assertThat(ImmutableList.copyOf(output.split("\n"))) + .containsExactly( + "Hello, world. An argument.", + "1970-01-01T00:00:01Z", + "rw-r--r--", + "rw-r--r--", + "foo", + "cat", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01Z"); assertDockerInspect(targetImage); assertThat(JibRunHelper.getCreationTime(targetImage)).isEqualTo(Instant.EPOCH); assertThat(getWorkingDirectory(targetImage)).isEqualTo("/home"); } + + @Test + public void testCredHelperConfiguration() + throws DigestException, IOException, InterruptedException { + String targetImage = "simpleimage:gradle" + System.nanoTime(); + assertThat( + JibRunHelper.buildToDockerDaemonAndRun( + simpleTestProject, targetImage, "build-cred-helper.gradle")) + .isEqualTo("Hello, world. \n1970-01-01T00:00:01Z\n"); + } + + @Test + public void testToDockerDaemon_multiPlatform() + throws DigestException, IOException, InterruptedException { + String targetImage = "multiplatform:gradle" + System.nanoTime(); + assertThat( + JibRunHelper.buildToDockerDaemonAndRun( + simpleTestProject, targetImage, "build-multi-platform.gradle")) + .isEqualTo("Hello, world. \n1970-01-01T00:00:01Z\n"); + } } diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SpringBootProjectIntegrationTest.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SpringBootProjectIntegrationTest.java index 9686ef8ac6..dca766587b 100644 --- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SpringBootProjectIntegrationTest.java +++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SpringBootProjectIntegrationTest.java @@ -18,6 +18,7 @@ import com.google.cloud.tools.jib.Command; import com.google.cloud.tools.jib.IntegrationTestingConfiguration; +import com.google.cloud.tools.jib.api.HttpRequestTester; import java.io.IOException; import java.net.URL; import java.security.DigestException; @@ -54,9 +55,11 @@ public void testBuild_packagedMode() throws IOException, InterruptedException, D "-c", "/app/classpath/spring-boot-original.jar") .run(); - Assert.assertEquals("1360 /app/classpath/spring-boot-original.jar\n", output); - Assert.assertEquals("Hello world", JibRunHelper.getContent(new URL("http://localhost:8080"))); + Assert.assertEquals("1360 /app/classpath/spring-boot-original.jar\n", output); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080")); } private void buildAndRunWebApp(String label, String gradleBuildFile) diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/WarProjectIntegrationTest.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/WarProjectIntegrationTest.java index 01fc3dfb08..dbcf72940b 100644 --- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/WarProjectIntegrationTest.java +++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/WarProjectIntegrationTest.java @@ -18,12 +18,12 @@ import com.google.cloud.tools.jib.Command; import com.google.cloud.tools.jib.IntegrationTestingConfiguration; +import com.google.cloud.tools.jib.api.HttpRequestTester; import java.io.IOException; import java.net.URL; import java.security.DigestException; import javax.annotation.Nullable; import org.junit.After; -import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; @@ -60,7 +60,8 @@ private void verifyBuildAndRun(TestProject project, String label, String gradleB JibRunHelper.buildAndRun(project, targetImage, gradleBuildFile, "--detach", "-p8080:8080"); containerName = output.trim(); - Assert.assertEquals( - "Hello world", JibRunHelper.getContent(new URL("http://localhost:8080/hello"))); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080/hello")); } } diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/build.gradle index 61b220e3a4..82b0405ecd 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/default-target/build.gradle @@ -11,10 +11,11 @@ repositories { } dependencies { - compile files('libs/dependency-1.0.0.jar') + implementation files('libs/dependency-1.0.0.jar') } jib { + from.image = 'eclipse-temurin:8-jdk-focal' container { args = ['An argument.'] ports = ['1000/tcp', '2000-2003/udp'] diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/build.gradle index 8214cf0aa9..d5424994a1 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/empty/build.gradle @@ -13,8 +13,9 @@ repositories { } jib { + from.image = 'eclipse-temurin:8-jdk-focal' to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') credHelper = 'gcr' } container { @@ -24,7 +25,7 @@ jib { } } -def additionalTag = System.getProperty("_ADDITIONAL_TAG") +def additionalTag = System.getProperty('_ADDITIONAL_TAG') if (additionalTag != null && !additionalTag.isEmpty()) { - jib.to.tags = [System.getProperty("_ADDITIONAL_TAG")] + jib.to.tags = [System.getProperty('_ADDITIONAL_TAG')] } diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/a_packaged/build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/a_packaged/build.gradle index 4f11ab3491..786de565ae 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/a_packaged/build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/multiproject/a_packaged/build.gradle @@ -4,12 +4,12 @@ plugins { } dependencies { - compile project(':b_dependency') + implementation project(':b_dependency') } jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') credHelper = 'gcr' } container { diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-cred-helper.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-cred-helper.gradle new file mode 100644 index 0000000000..50bb30fa06 --- /dev/null +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-cred-helper.gradle @@ -0,0 +1,31 @@ +plugins { + id 'java' + id 'com.google.cloud.tools.jib' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + compile files('libs/dependency-1.0.0.jar') +} + +jib { + from { + image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96' + credHelper = 'gcr' + } + to { + image = System.getProperty("_TARGET_IMAGE") + credHelper { + helper = 'gcr' + environment = [ + ENV_VAR: 'A VAR' + ] + } + } +} diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-jar-containerization.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-jar-containerization.gradle index d18d6dff02..917a55ef7f 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-jar-containerization.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-jar-containerization.gradle @@ -24,8 +24,7 @@ jar { } jib { - to { - image = System.getProperty("_TARGET_IMAGE") - } + to.image = System.getProperty("_TARGET_IMAGE") + from.image = 'eclipse-temurin:11-jdk-focal' containerizingMode = 'packaged' } diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11-incompatible.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11-incompatible.gradle index ed86033374..2cc07b662e 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11-incompatible.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11-incompatible.gradle @@ -14,5 +14,5 @@ dependencies { compile files('libs/dependency-1.0.0.jar') } -jib.from.image = 'gcr.io/distroless/java' +jib.from.image = 'eclipse-temurin:8-jdk-focal' jib.to.image = System.getProperty("_TARGET_IMAGE") \ No newline at end of file diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11.gradle index 62b07c9c24..8c563bfb00 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java11.gradle @@ -14,4 +14,5 @@ dependencies { compile files('libs/dependency-1.0.0.jar') } +jib.from.image = 'eclipse-temurin:11-jdk-focal' jib.to.image = System.getProperty("_TARGET_IMAGE") \ No newline at end of file diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java17.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java17.gradle new file mode 100644 index 0000000000..9c860213ef --- /dev/null +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-java17.gradle @@ -0,0 +1,17 @@ +plugins { + id 'java' + id 'com.google.cloud.tools.jib' +} + +sourceCompatibility = 17 +targetCompatibility = 17 + +repositories { + mavenCentral() +} + +dependencies { + compile files('libs/dependency-1.0.0.jar') +} + +jib.to.image = System.getProperty("_TARGET_IMAGE") \ No newline at end of file diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle new file mode 100644 index 0000000000..5235164884 --- /dev/null +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' + id 'com.google.cloud.tools.jib' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + implementation files('libs/dependency-1.0.0.jar') +} + +jib { + from { + image = 'eclipse-temurin:11' + platforms { + platform { + architecture = 'amd64' + os = 'linux' + } + platform { + architecture = 'arm64' + os = 'linux' + } + } + } + to { + image = System.getProperty('_TARGET_IMAGE') + } +} diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-timestamps-custom.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-timestamps-custom.gradle index 6fc9bb42a4..9771046978 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-timestamps-custom.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-timestamps-custom.gradle @@ -15,7 +15,10 @@ dependencies { } jib { + from.image = 'eclipse-temurin:11-jdk-focal' to.image = System.getProperty("_TARGET_IMAGE") - container.filesModificationTime = '2011-12-03T10:15:30+09:00' - container.creationTime = '2013-11-05T06:29:30+09:00' + container { + filesModificationTime = '2011-12-03T10:15:30+09:00' + creationTime = '2013-11-05T06:29:30+09:00' + } } diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build.gradle index 6507b770ef..b6d172b976 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build.gradle @@ -11,13 +11,13 @@ repositories { } dependencies { - compile files('libs/dependency-1.0.0.jar') + implementation files('libs/dependency-1.0.0.jar') } jib { from.image = 'gcr.io/distroless/java@sha256:2315ed1472a09826c1f31ab93ff13ceaa3a4e7d5482f357d15a296b3db0d1c96' to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') credHelper = 'gcr' } container { diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/complex-build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/complex-build.gradle index 715e294ca9..c87182c454 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/complex-build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/complex-build.gradle @@ -11,12 +11,14 @@ repositories { } dependencies { - compile files('libs/dependency-1.0.0.jar') + implementation files('libs/dependency-1.0.0.jar') } + jib { from { - image = 'localhost:5000/distroless/java' + def dockerHost = System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost" + image = dockerHost + ':5000/distroless/java' auth { username = 'testuser' password = 'testpassword' diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/mock-docker.sh b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/mock-docker.sh index e4b78a0f4c..84eafa19a8 100755 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/mock-docker.sh +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/mock-docker.sh @@ -1,5 +1,11 @@ #!/bin/bash +if [[ "$1" == "info" ]]; then + # Output the JSON string + echo "{\"OSType\":\"linux\",\"Architecture\":\"x86_64\"}" + exit 0 +fi + # Read stdin to avoid broken pipe cat > /dev/null diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/build.gradle index 85ec587126..2dda8a4d31 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/build.gradle @@ -18,7 +18,7 @@ dependencies { jib { from.image = 'gcr.io/distroless/java:debug' - to.image = System.getProperty("_TARGET_IMAGE") + to.image = System.getProperty('_TARGET_IMAGE') to.credHelper = 'gcr' containerizingMode='packaged' } diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/settings.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/settings.gradle index ca13736740..17fb2a2bfa 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/settings.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/spring-boot/settings.gradle @@ -1 +1,7 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} rootProject.name = 'spring-boot' \ No newline at end of file diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build-tomcat.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build-tomcat.gradle index 97065475e5..c8687a8ec5 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build-tomcat.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build-tomcat.gradle @@ -16,8 +16,8 @@ configurations { } dependencies { - providedCompile 'javax.servlet:servlet-api:2.5' - moreLibs 'javax.annotation:javax.annotation-api:1.2' // random extra JAR + providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0' + moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR } war { @@ -27,7 +27,7 @@ war { } jib { - from.image = 'tomcat:8.5-jre8-alpine' + from.image = 'tomcat:10-jre8-temurin-focal' to { image = System.getProperty("_TARGET_IMAGE") credHelper = 'gcr' diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build.gradle index 5960808868..9d64daa74e 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build.gradle +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/build.gradle @@ -16,8 +16,8 @@ configurations { } dependencies { - providedCompile 'javax.servlet:servlet-api:2.5' - moreLibs 'javax.annotation:javax.annotation-api:1.2' // random extra JAR + providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0' + moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR } war { @@ -28,7 +28,7 @@ war { jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') credHelper = 'gcr' } } diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java index 05618c6777..ae65db32f0 100644 --- a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java +++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java @@ -16,6 +16,9 @@ package example; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -23,9 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class HelloWorld extends HttpServlet { diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BaseImageParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BaseImageParameters.java index 9077e5b147..0dad3f2a49 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BaseImageParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BaseImageParameters.java @@ -16,7 +16,10 @@ package com.google.cloud.tools.jib.gradle; +import com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator; import com.google.cloud.tools.jib.plugins.common.PropertyNames; +import java.util.List; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; import org.gradle.api.Action; @@ -32,8 +35,8 @@ public class BaseImageParameters { private final AuthParameters auth; - private Property image; - @Nullable private String credHelper; + private final Property image; + private final CredHelperParameters credHelper; private final PlatformParametersSpec platformParametersSpec; private final ListProperty platforms; @@ -43,6 +46,8 @@ public BaseImageParameters(ObjectFactory objectFactory) { platforms = objectFactory.listProperty(PlatformParameters.class); image = objectFactory.property(String.class); platformParametersSpec = objectFactory.newInstance(PlatformParametersSpec.class, platforms); + credHelper = + objectFactory.newInstance(CredHelperParameters.class, PropertyNames.FROM_CRED_HELPER); PlatformParameters amd64Linux = new PlatformParameters(); amd64Linux.setArchitecture("amd64"); @@ -53,6 +58,16 @@ public BaseImageParameters(ObjectFactory objectFactory) { @Nested @Optional public ListProperty getPlatforms() { + String property = System.getProperty(PropertyNames.FROM_PLATFORMS); + if (property != null) { + List parsed = + ConfigurationPropertyValidator.parseListProperty(property).stream() + .map(PlatformParameters::of) + .collect(Collectors.toList()); + if (!parsed.equals(platforms.get())) { + platforms.set(parsed); + } + } return platforms; } @@ -79,18 +94,18 @@ public void setImage(Provider image) { this.image.set(image); } - @Input - @Nullable + @Nested @Optional - public String getCredHelper() { - if (System.getProperty(PropertyNames.FROM_CRED_HELPER) != null) { - return System.getProperty(PropertyNames.FROM_CRED_HELPER); - } + public CredHelperParameters getCredHelper() { return credHelper; } - public void setCredHelper(String credHelper) { - this.credHelper = credHelper; + public void setCredHelper(String helper) { + this.credHelper.setHelper(helper); + } + + public void credHelper(Action action) { + action.execute(credHelper); } @Nested diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java index 41a92616b2..692449db03 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildDockerTask.java @@ -18,9 +18,10 @@ import com.google.cloud.tools.jib.api.CacheDirectoryCreationException; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.docker.CliDockerClient; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException; +import com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException; import com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions; import com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException; import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; @@ -95,8 +96,8 @@ public void buildDocker() Path dockerExecutable = jibExtension.getDockerClient().getExecutablePath(); boolean isDockerInstalled = dockerExecutable == null - ? DockerClient.isDefaultDockerInstalled() - : DockerClient.isDockerInstalled(dockerExecutable); + ? CliDockerClient.isDefaultDockerInstalled() + : CliDockerClient.isDockerInstalled(dockerExecutable); if (!isDockerInstalled) { throw new GradleException( HelpfulSuggestions.forDockerNotInstalled(HELPFUL_SUGGESTIONS_PREFIX)); @@ -180,6 +181,11 @@ public void buildDocker() throw new GradleException( HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex); + } catch (ExtraDirectoryNotFoundException ex) { + throw new GradleException( + "extraDirectories.paths contain \"from\" directory that doesn't exist locally: " + + ex.getPath(), + ex); } finally { tempDirectoryProvider.close(); TaskCommon.finishUpdateChecker(projectProperties, updateCheckFuture); diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java index f8d8a7c006..49265559a6 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildImageTask.java @@ -20,6 +20,7 @@ import com.google.cloud.tools.jib.api.InvalidImageReferenceException; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException; +import com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException; import com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions; import com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException; import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; @@ -175,6 +176,11 @@ public void buildImage() throw new GradleException( HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex); + } catch (ExtraDirectoryNotFoundException ex) { + throw new GradleException( + "extraDirectories.paths contain \"from\" directory that doesn't exist locally: " + + ex.getPath(), + ex); } finally { tempDirectoryProvider.close(); TaskCommon.finishUpdateChecker(projectProperties, updateCheckFuture); diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java index 006726eb51..1a77856b6b 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/BuildTarTask.java @@ -20,6 +20,7 @@ import com.google.cloud.tools.jib.api.InvalidImageReferenceException; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException; +import com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException; import com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions; import com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException; import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; @@ -197,6 +198,11 @@ public void buildTar() throw new GradleException( HelpfulSuggestions.forInvalidImageReference(ex.getInvalidReference()), ex); + } catch (ExtraDirectoryNotFoundException ex) { + throw new GradleException( + "extraDirectories.paths contain \"from\" directory that doesn't exist locally: " + + ex.getPath(), + ex); } finally { tempDirectoryProvider.close(); TaskCommon.finishUpdateChecker(projectProperties, updateCheckFuture); diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java index c8aa5e63ca..095da5008a 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ContainerParameters.java @@ -26,7 +26,10 @@ import javax.annotation.Nullable; import javax.inject.Inject; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; @@ -36,12 +39,12 @@ */ public class ContainerParameters { - private List jvmFlags = Collections.emptyList(); + private final ListProperty jvmFlags; private Map environment = Collections.emptyMap(); - @Nullable private List entrypoint; + private ListProperty entrypoint; private List extraClasspath = Collections.emptyList(); private boolean expandClasspathDependencies; - @Nullable private String mainClass; + private final Property mainClass; @Nullable private List args; private ImageFormat format = ImageFormat.Docker; private List ports = Collections.emptyList(); @@ -50,12 +53,17 @@ public class ContainerParameters { private String appRoot = ""; @Nullable private String user; @Nullable private String workingDirectory; - private String filesModificationTime = "EPOCH_PLUS_SECOND"; - private String creationTime = "EPOCH"; + private final Property filesModificationTime; + private final Property creationTime; @Inject public ContainerParameters(ObjectFactory objectFactory) { labels = objectFactory.mapProperty(String.class, String.class).empty(); + filesModificationTime = objectFactory.property(String.class).convention("EPOCH_PLUS_SECOND"); + creationTime = objectFactory.property(String.class).convention("EPOCH"); + mainClass = objectFactory.property(String.class); + jvmFlags = objectFactory.listProperty(String.class); + entrypoint = objectFactory.listProperty(String.class); } @Input @@ -66,29 +74,37 @@ public List getEntrypoint() { return ConfigurationPropertyValidator.parseListProperty( System.getProperty(PropertyNames.CONTAINER_ENTRYPOINT)); } - return entrypoint; + return entrypoint.getOrNull(); } public void setEntrypoint(List entrypoint) { - this.entrypoint = entrypoint; + this.entrypoint.set(entrypoint); + } + + public void setEntrypoint(Provider> entrypoint) { + this.entrypoint.set(entrypoint); } public void setEntrypoint(String entrypoint) { - this.entrypoint = Collections.singletonList(entrypoint); + this.entrypoint.set(Collections.singletonList(entrypoint)); } @Input @Optional public List getJvmFlags() { - if (System.getProperty(PropertyNames.CONTAINER_JVM_FLAGS) != null) { - return ConfigurationPropertyValidator.parseListProperty( - System.getProperty(PropertyNames.CONTAINER_JVM_FLAGS)); + String jvmFlagsSystemProperty = System.getProperty(PropertyNames.CONTAINER_JVM_FLAGS); + if (jvmFlagsSystemProperty != null) { + return ConfigurationPropertyValidator.parseListProperty(jvmFlagsSystemProperty); } - return jvmFlags; + return jvmFlags.getOrElse(Collections.emptyList()); } public void setJvmFlags(List jvmFlags) { - this.jvmFlags = jvmFlags; + this.jvmFlags.set(jvmFlags); + } + + public void setJvmFlags(Provider> jvmFlags) { + this.jvmFlags.set(jvmFlags); } @Input @@ -135,14 +151,19 @@ public void setExpandClasspathDependencies(boolean expand) { @Nullable @Optional public String getMainClass() { - if (System.getProperty(PropertyNames.CONTAINER_MAIN_CLASS) != null) { - return System.getProperty(PropertyNames.CONTAINER_MAIN_CLASS); + String mainClassProperty = System.getProperty(PropertyNames.CONTAINER_MAIN_CLASS); + if (mainClassProperty != null) { + return mainClassProperty; } - return mainClass; + return mainClass.getOrNull(); } public void setMainClass(String mainClass) { - this.mainClass = mainClass; + this.mainClass.set(mainClass); + } + + public void setMainClass(Provider mainClass) { + this.mainClass.set(mainClass); } @Input @@ -173,6 +194,10 @@ public void setFormat(ImageFormat format) { this.format = format; } + public void setFormat(String format) { + this.format = ImageFormat.valueOf(format); + } + @Input @Optional public List getPorts() { @@ -258,27 +283,21 @@ public void setWorkingDirectory(String workingDirectory) { @Input @Optional - public String getFilesModificationTime() { - if (System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME) != null) { - return System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME); + public Property getFilesModificationTime() { + String property = System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME); + if (property != null && !property.equals(filesModificationTime.get())) { + filesModificationTime.set(property); } return filesModificationTime; } - public void setFilesModificationTime(String filesModificationTime) { - this.filesModificationTime = filesModificationTime; - } - @Input @Optional - public String getCreationTime() { - if (System.getProperty(PropertyNames.CONTAINER_CREATION_TIME) != null) { - return System.getProperty(PropertyNames.CONTAINER_CREATION_TIME); + public Property getCreationTime() { + String property = System.getProperty(PropertyNames.CONTAINER_CREATION_TIME); + if (property != null && !property.equals(creationTime.get())) { + creationTime.set(property); } return creationTime; } - - public void setCreationTime(String creationTime) { - this.creationTime = creationTime; - } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/CredHelperParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/CredHelperParameters.java new file mode 100644 index 0000000000..8a972f719a --- /dev/null +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/CredHelperParameters.java @@ -0,0 +1,76 @@ +/* + * Copyright 2018 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.gradle; + +import com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration; +import java.util.Map; +import javax.annotation.Nullable; +import javax.inject.Inject; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; + +/** Configuration for a credential helper. */ +public class CredHelperParameters implements CredHelperConfiguration { + private final String propertyName; + private final MapProperty environment; + @Nullable private String helper; + + @Inject + public CredHelperParameters(ObjectFactory objectFactory, String propertyName) { + this.propertyName = propertyName; + environment = objectFactory.mapProperty(String.class, String.class).empty(); + } + + @Input + @Nullable + @Optional + public String getHelper() { + if (System.getProperty(propertyName) != null) { + return System.getProperty(propertyName); + } + return helper; + } + + @Internal + @Override + public java.util.Optional getHelperName() { + return java.util.Optional.ofNullable(getHelper()); + } + + public void setHelper(String helper) { + this.helper = helper; + } + + @Override + @Input + @Optional + public Map getEnvironment() { + return environment.get(); + } + + public void setEnvironment(Map environment) { + this.environment.set(environment); + } + + public void setEnvironment(Provider> environment) { + this.environment.set(environment); + } +} diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoriesParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoriesParameters.java index ae65353580..30dc526e89 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoriesParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoriesParameters.java @@ -24,11 +24,14 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; @@ -40,7 +43,7 @@ public class ExtraDirectoriesParameters { private ListProperty paths; private ExtraDirectoryParametersSpec spec; - private Map permissions = Collections.emptyMap(); + private MapProperty permissions; @Inject public ExtraDirectoriesParameters(ObjectFactory objects, Project project) { @@ -48,6 +51,7 @@ public ExtraDirectoriesParameters(ObjectFactory objects, Project project) { this.project = project; paths = objects.listProperty(ExtraDirectoryParameters.class).empty(); spec = objects.newInstance(ExtraDirectoryParametersSpec.class, project, paths); + permissions = objects.mapProperty(String.class, String.class).empty(); } public void paths(Action action) { @@ -92,10 +96,28 @@ public List getPaths() { * @param paths paths to set. */ public void setPaths(Object paths) { - this.paths.set( - project.files(paths).getFiles().stream() - .map(file -> new ExtraDirectoryParameters(objects, project, file.toPath(), "/")) - .collect(Collectors.toList())); + this.paths.set(convertToExtraDirectoryParametersList(paths)); + } + + /** + * Sets paths, for lazy evaluation where {@code paths} is a {@link Provider} of a suitable object. + * + * @param paths provider of paths to set + * @see #setPaths(Object) + */ + public void setPaths(Provider paths) { + this.paths.set(paths.map(this::convertToExtraDirectoryParametersList)); + } + + /** + * Helper method to convert {@code Object} to {@code List} in {@code + * setFrom}. + */ + @Nonnull + private List convertToExtraDirectoryParametersList(Object obj) { + return project.files(obj).getFiles().stream() + .map(file -> new ExtraDirectoryParameters(objects, project, file.toPath(), "/")) + .collect(Collectors.toList()); } /** @@ -106,15 +128,15 @@ public void setPaths(Object paths) { * @return the permissions map from path on container to file permissions */ @Input - public Map getPermissions() { + public MapProperty getPermissions() { String property = System.getProperty(PropertyNames.EXTRA_DIRECTORIES_PERMISSIONS); if (property != null) { - return ConfigurationPropertyValidator.parseMapProperty(property); + Map parsedPermissions = + ConfigurationPropertyValidator.parseMapProperty(property); + if (!parsedPermissions.equals(permissions.get())) { + permissions.set(parsedPermissions); + } } return permissions; } - - public void setPermissions(Map permissions) { - this.permissions = permissions; - } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoryParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoryParameters.java index 008fd53b85..537b51f085 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoryParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/ExtraDirectoryParameters.java @@ -24,6 +24,8 @@ import org.gradle.api.Project; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; @@ -31,49 +33,59 @@ public class ExtraDirectoryParameters implements ExtraDirectoriesConfiguration { private Project project; - private Path from = Paths.get(""); - private String into = "/"; + private Property from; + private Property into; private ListProperty includes; private ListProperty excludes; @Inject public ExtraDirectoryParameters(ObjectFactory objects, Project project) { this.project = project; - includes = objects.listProperty(String.class).empty(); - excludes = objects.listProperty(String.class).empty(); + this.from = objects.property(Path.class).value(Paths.get("")); + this.into = objects.property(String.class).value("/"); + this.includes = objects.listProperty(String.class).empty(); + this.excludes = objects.listProperty(String.class).empty(); } ExtraDirectoryParameters(ObjectFactory objects, Project project, Path from, String into) { this(objects, project); - this.from = from; - this.into = into; + this.from = objects.property(Path.class).value(from); + this.into = objects.property(String.class).value(into); } @Input public String getFromString() { // Gradle warns about @Input annotations on File objects, so we have to expose a getter for a // String to make them go away. - return from.toString(); + return from.get().toString(); } @Override @Internal public Path getFrom() { - return from; + return from.get(); } public void setFrom(Object from) { - this.from = project.file(from).toPath(); + this.from.set(project.file(from).toPath()); + } + + public void setFrom(Provider from) { + this.from.set(from.map(obj -> project.file(obj).toPath())); } @Override @Input public String getInto() { - return into; + return into.get(); } public void setInto(String into) { - this.into = into; + this.into.set(into); + } + + public void setInto(Provider into) { + this.into.set(into); } @Input diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java index 2096617316..6a1978fe19 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleProjectProperties.java @@ -70,10 +70,11 @@ import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.file.FileCollection; import org.gradle.api.logging.Logger; -import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.WarPlugin; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.jvm.tasks.Jar; @@ -292,7 +293,7 @@ public List getClassFiles() throws IOException { getMainSourceSet().getOutput().getClassesDirs().filter(File::exists); List classFiles = new ArrayList<>(); for (File classesOutputDirectory : classesOutputDirectories) { - classFiles.addAll(new DirectoryWalker(classesOutputDirectory.toPath()).walk().asList()); + classFiles.addAll(new DirectoryWalker(classesOutputDirectory.toPath()).walk()); } return classFiles; } @@ -397,17 +398,26 @@ public boolean isWarProject() { } /** - * Returns the input files for a task. These files include the runtimeClasspath of the application - * and any extraDirectories defined by the user to include in the container. + * Returns the input files for a task. These files include the gradle {@link + * org.gradle.api.artifacts.Configuration}, output directories (classes, resources, etc.) of the + * main {@link org.gradle.api.tasks.SourceSet}, and any extraDirectories defined by the user to + * include in the container. * * @param project the gradle project * @param extraDirectories the image's configured extra directories * @return the input files */ + @VisibleForTesting static FileCollection getInputFiles( Project project, List extraDirectories, String configurationName) { List dependencyFileCollections = new ArrayList<>(); dependencyFileCollections.add(project.getConfigurations().getByName(configurationName)); + // Output directories (classes and resources) from main SourceSet are added + // so that BuildTarTask picks up changes in these and do not skip task + SourceSetContainer sourceSetContainer = + project.getExtensions().getByType(SourceSetContainer.class); + SourceSet mainSourceSet = sourceSetContainer.getByName(MAIN_SOURCE_SET_NAME); + dependencyFileCollections.add(mainSourceSet.getOutput()); extraDirectories.stream() .filter(Files::exists) @@ -431,10 +441,10 @@ public String getVersion() { @Override public int getMajorJavaVersion() { JavaVersion version = JavaVersion.current(); - JavaPluginConvention javaPluginConvention = - project.getConvention().findPlugin(JavaPluginConvention.class); - if (javaPluginConvention != null) { - version = javaPluginConvention.getTargetCompatibility(); + JavaPluginExtension javaPluginExtension = + project.getExtensions().findByType(JavaPluginExtension.class); + if (javaPluginExtension != null) { + version = javaPluginExtension.getTargetCompatibility(); } return Integer.valueOf(version.getMajorVersion()); } @@ -535,8 +545,8 @@ private JibGradlePluginExtension findConfiguredExtension( } private SourceSet getMainSourceSet() { - JavaPluginConvention javaPluginConvention = - project.getConvention().getPlugin(JavaPluginConvention.class); - return javaPluginConvention.getSourceSets().getByName(MAIN_SOURCE_SET_NAME); + SourceSetContainer sourceSetContainer = + project.getExtensions().getByType(SourceSetContainer.class); + return sourceSetContainer.getByName(MAIN_SOURCE_SET_NAME); } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java index 914cfe5881..10195cc756 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java @@ -47,8 +47,8 @@ public AuthProperty getFromAuth() { } @Override - public Optional getFromCredHelper() { - return Optional.ofNullable(jibExtension.getFrom().getCredHelper()); + public CredHelperConfiguration getFromCredHelper() { + return jibExtension.getFrom().getCredHelper(); } @Override @@ -62,8 +62,8 @@ public AuthProperty getToAuth() { } @Override - public Optional getToCredHelper() { - return Optional.ofNullable(jibExtension.getTo().getCredHelper()); + public CredHelperConfiguration getToCredHelper() { + return jibExtension.getTo().getCredHelper(); } @Override @@ -153,12 +153,12 @@ public Optional getProperty(String propertyName) { @Override public String getFilesModificationTime() { - return jibExtension.getContainer().getFilesModificationTime(); + return jibExtension.getContainer().getFilesModificationTime().get(); } @Override public String getCreationTime() { - return jibExtension.getContainer().getCreationTime(); + return jibExtension.getContainer().getCreationTime().get(); } @Override @@ -174,7 +174,8 @@ public List getExtraDirectories() { @Override public Map getExtraDirectoryPermissions() { - return TaskCommon.convertPermissionsMap(jibExtension.getExtraDirectories().getPermissions()); + return TaskCommon.convertPermissionsMap( + jibExtension.getExtraDirectories().getPermissions().get()); } @Override diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibPlugin.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibPlugin.java index aa6706dd4f..6f2d9d16d1 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibPlugin.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/JibPlugin.java @@ -30,8 +30,8 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; -import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; @@ -184,9 +184,8 @@ public void apply(Project project) { SourceSet mainSourceSet = projectAfterEvaluation - .getConvention() - .getPlugin(JavaPluginConvention.class) - .getSourceSets() + .getExtensions() + .getByType(SourceSetContainer.class) .getByName(SourceSet.MAIN_SOURCE_SET_NAME); jibDependencies.add(mainSourceSet.getRuntimeClasspath()); jibDependencies.add( diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/PlatformParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/PlatformParameters.java index 204d0c20ad..7b65978e59 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/PlatformParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/PlatformParameters.java @@ -17,13 +17,28 @@ package com.google.cloud.tools.jib.gradle; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration; +import java.util.Objects; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nullable; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Internal; /** Configuration of a platform. */ public class PlatformParameters implements PlatformConfiguration { + + static PlatformParameters of(String osArchitecture) { + Matcher matcher = Pattern.compile("([^/ ]+)/([^/ ]+)").matcher(osArchitecture); + if (!matcher.matches()) { + throw new IllegalArgumentException("Platform must be of form os/architecture."); + } + PlatformParameters platformParameters = new PlatformParameters(); + platformParameters.os = matcher.group(1); + platformParameters.architecture = matcher.group(2); + return platformParameters; + } + @Nullable private String os; @Nullable private String architecture; @@ -58,4 +73,22 @@ public Optional getArchitectureName() { public void setArchitecture(String architecture) { this.architecture = architecture; } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof PlatformParameters)) { + return false; + } + PlatformParameters otherPlatform = (PlatformParameters) other; + return Objects.equals(architecture, otherPlatform.getArchitecture()) + && Objects.equals(os, otherPlatform.getOs()); + } + + @Override + public int hashCode() { + return Objects.hash(architecture, os); + } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/TargetImageParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/TargetImageParameters.java index 9fd0e94a59..684f7f4b21 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/TargetImageParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/TargetImageParameters.java @@ -36,16 +36,17 @@ public class TargetImageParameters { private final AuthParameters auth; - - private Property image; - private SetProperty tags; - @Nullable private String credHelper; + private final Property image; + private final SetProperty tags; + private final CredHelperParameters credHelper; @Inject public TargetImageParameters(ObjectFactory objectFactory) { auth = objectFactory.newInstance(AuthParameters.class, "to.auth"); image = objectFactory.property(String.class); tags = objectFactory.setProperty(String.class).empty(); + credHelper = + objectFactory.newInstance(CredHelperParameters.class, PropertyNames.TO_CRED_HELPER); } @Input @@ -98,18 +99,18 @@ public void setTags(Provider> tags) { this.tags.set(tags); } - @Input - @Nullable + @Nested @Optional - public String getCredHelper() { - if (System.getProperty(PropertyNames.TO_CRED_HELPER) != null) { - return System.getProperty(PropertyNames.TO_CRED_HELPER); - } + public CredHelperParameters getCredHelper() { return credHelper; } - public void setCredHelper(String credHelper) { - this.credHelper = credHelper; + public void setCredHelper(String helper) { + this.credHelper.setHelper(helper); + } + + public void credHelper(Action action) { + action.execute(credHelper); } @Nested diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java index 81a738a53e..fc15b021cf 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/skaffold/FilesTaskV2.java @@ -38,8 +38,8 @@ import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.initialization.Settings; -import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskAction; /** @@ -162,11 +162,10 @@ private void addProjectFiles(Project project) { addGradleFiles(project); // Add sources + resources - JavaPluginConvention javaConvention = - project.getConvention().findPlugin(JavaPluginConvention.class); - if (javaConvention != null) { - SourceSet mainSourceSet = - javaConvention.getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME); + SourceSetContainer sourceSetContainer = + project.getExtensions().findByType(SourceSetContainer.class); + if (sourceSetContainer != null) { + SourceSet mainSourceSet = sourceSetContainer.findByName(SourceSet.MAIN_SOURCE_SET_NAME); if (mainSourceSet != null) { mainSourceSet .getAllSource() diff --git a/jib-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.google.cloud.tools.jib.properties b/jib-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.google.cloud.tools.jib.properties deleted file mode 100644 index 9f1e4df0c3..0000000000 --- a/jib-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.google.cloud.tools.jib.properties +++ /dev/null @@ -1,17 +0,0 @@ -# - * Copyright 2018 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. -# - -implementation-class=com.google.cloud.tools.jib.gradle.JibPlugin diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java index 3095e906be..7698228ab0 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleProjectPropertiesTest.java @@ -51,6 +51,7 @@ import java.nio.file.Paths; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; @@ -62,10 +63,11 @@ import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.file.FileCollection; import org.gradle.api.java.archives.internal.DefaultManifest; import org.gradle.api.logging.Logger; import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.Property; import org.gradle.api.tasks.bundling.War; import org.gradle.jvm.tasks.Jar; @@ -89,6 +91,8 @@ public class GradleProjectPropertiesTest { private static final Correspondence EXTRACTION_PATH_OF = Correspondence.transforming( entry -> entry.getExtractionPath().toString(), "has extractionPath of"); + private static final Correspondence FILE_PATH_OF = + Correspondence.transforming(File::toPath, "has Path of"); private static final Instant EPOCH_PLUS_32 = Instant.ofEpochSecond(32); @@ -144,7 +148,7 @@ public void setUp() throws URISyntaxException, IOException { DependencyHandler dependencies = project.getDependencies(); dependencies.add( - "compile", + "implementation", project.files( "dependencies/library.jarC.jar", "dependencies/libraryB.jar", @@ -208,6 +212,30 @@ public void testIsWarProject() { assertThat(gradleProjectProperties.isWarProject()).isTrue(); } + @Test + public void testGetInputFiles() throws URISyntaxException { + Path applicationDirectory = getResource("gradle/application"); + + List extraDirectories = Arrays.asList(applicationDirectory.resolve("extra-directory")); + FileCollection fileCollection = + GradleProjectProperties.getInputFiles( + project, extraDirectories, JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + + assertThat(fileCollection) + .comparingElementsUsing(FILE_PATH_OF) + .containsExactly( + applicationDirectory.resolve("build/classes/java/main"), + applicationDirectory.resolve("build/resources/main"), + applicationDirectory.resolve("dependencies/dependencyX-1.0.0-SNAPSHOT.jar"), + applicationDirectory.resolve("dependencies/dependency-1.0.0.jar"), + applicationDirectory.resolve("dependencies/more/dependency-1.0.0.jar"), + applicationDirectory.resolve("dependencies/another/one/dependency-1.0.0.jar"), + applicationDirectory.resolve("dependencies/libraryA.jar"), + applicationDirectory.resolve("dependencies/libraryB.jar"), + applicationDirectory.resolve("dependencies/library.jarC.jar"), + applicationDirectory.resolve("extra-directory")); + } + @Test public void testConvertPermissionsMap() { Map map = ImmutableMap.of("/test/folder/file1", "123", "/test/file2", "456"); @@ -230,16 +258,15 @@ public void testConvertPermissionsMap() { @Test public void testGetMajorJavaVersion() { - JavaPluginConvention convention = - project.getConvention().findPlugin(JavaPluginConvention.class); + JavaPluginExtension extension = project.getExtensions().findByType(JavaPluginExtension.class); - convention.setTargetCompatibility(JavaVersion.VERSION_1_3); + extension.setTargetCompatibility(JavaVersion.VERSION_1_3); assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(3); - convention.setTargetCompatibility(JavaVersion.VERSION_11); + extension.setTargetCompatibility(JavaVersion.VERSION_11); assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(11); - convention.setTargetCompatibility(JavaVersion.VERSION_1_9); + extension.setTargetCompatibility(JavaVersion.VERSION_1_9); assertThat(gradleProjectProperties.getMajorJavaVersion()).isEqualTo(9); } diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java index aa3aa2e28b..8655241553 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java @@ -24,7 +24,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Optional; import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,6 +50,10 @@ public void testGetters() { ContainerParameters containerParameters = Mockito.mock(ContainerParameters.class); DockerClientParameters dockerClientParameters = Mockito.mock(DockerClientParameters.class); OutputPathsParameters outputPathsParameters = Mockito.mock(OutputPathsParameters.class); + CredHelperParameters fromCredHelperParameters = Mockito.mock(CredHelperParameters.class); + CredHelperParameters toCredHelperParameters = Mockito.mock(CredHelperParameters.class); + Property filesModificationTime = Mockito.mock(Property.class); + Property creationTime = Mockito.mock(Property.class); Mockito.when(authParameters.getUsername()).thenReturn("user"); Mockito.when(authParameters.getPassword()).thenReturn("password"); @@ -62,12 +68,19 @@ public void testGetters() { Mockito.when(jibExtension.getOutputPaths()).thenReturn(outputPathsParameters); Mockito.when(jibExtension.getAllowInsecureRegistries()).thenReturn(true); - Mockito.when(baseImageParameters.getCredHelper()).thenReturn("gcr"); + Mockito.when(fromCredHelperParameters.getHelperName()).thenReturn(Optional.of("gcr")); + Mockito.when(fromCredHelperParameters.getEnvironment()) + .thenReturn(Collections.singletonMap("ENV_VARIABLE", "Value1")); + Mockito.when(baseImageParameters.getCredHelper()).thenReturn(fromCredHelperParameters); Mockito.when(baseImageParameters.getImage()).thenReturn("openjdk:15"); Mockito.when(baseImageParameters.getAuth()).thenReturn(authParameters); Mockito.when(targetImageParameters.getTags()) .thenReturn(new HashSet<>(Arrays.asList("additional", "tags"))); + Mockito.when(toCredHelperParameters.getHelperName()).thenReturn(Optional.of("ecr-login")); + Mockito.when(toCredHelperParameters.getEnvironment()) + .thenReturn(Collections.singletonMap("ENV_VARIABLE", "Value2")); + Mockito.when(targetImageParameters.getCredHelper()).thenReturn(toCredHelperParameters); Mockito.when(containerParameters.getAppRoot()).thenReturn("/app/root"); Mockito.when(containerParameters.getArgs()).thenReturn(Arrays.asList("--log", "info")); @@ -80,7 +93,10 @@ public void testGetters() { Mockito.when(containerParameters.getMainClass()).thenReturn("com.example.Main"); Mockito.when(containerParameters.getPorts()).thenReturn(Arrays.asList("80/tcp", "0")); Mockito.when(containerParameters.getUser()).thenReturn("admin:wheel"); - Mockito.when(containerParameters.getFilesModificationTime()).thenReturn("2011-12-03T22:42:05Z"); + Mockito.when(containerParameters.getFilesModificationTime()).thenReturn(filesModificationTime); + Mockito.when(filesModificationTime.get()).thenReturn("2011-12-03T22:42:05Z"); + Mockito.when(containerParameters.getCreationTime()).thenReturn(creationTime); + Mockito.when(creationTime.get()).thenReturn("2011-12-03T11:42:05Z"); Mockito.when(dockerClientParameters.getExecutablePath()).thenReturn(Paths.get("test")); Mockito.when(dockerClientParameters.getEnvironment()) @@ -105,7 +121,10 @@ public void testGetters() { Assert.assertEquals(Arrays.asList("java", "Main"), rawConfiguration.getEntrypoint().get()); Assert.assertEquals( new HashMap<>(ImmutableMap.of("currency", "dollar")), rawConfiguration.getEnvironment()); - Assert.assertEquals("gcr", rawConfiguration.getFromCredHelper().get()); + Assert.assertEquals("gcr", rawConfiguration.getFromCredHelper().getHelperName().get()); + Assert.assertEquals( + Collections.singletonMap("ENV_VARIABLE", "Value1"), + rawConfiguration.getFromCredHelper().getEnvironment()); Assert.assertEquals("openjdk:15", rawConfiguration.getFromImage().get()); Assert.assertEquals(Arrays.asList("-cp", "."), rawConfiguration.getJvmFlags()); Assert.assertEquals(new HashMap<>(ImmutableMap.of("unit", "cm")), rawConfiguration.getLabels()); @@ -116,8 +135,13 @@ public void testGetters() { Assert.assertEquals( new HashSet<>(Arrays.asList("additional", "tags")), Sets.newHashSet(rawConfiguration.getToTags())); + Assert.assertEquals("ecr-login", rawConfiguration.getToCredHelper().getHelperName().get()); + Assert.assertEquals( + Collections.singletonMap("ENV_VARIABLE", "Value2"), + rawConfiguration.getToCredHelper().getEnvironment()); Assert.assertEquals("admin:wheel", rawConfiguration.getUser().get()); Assert.assertEquals("2011-12-03T22:42:05Z", rawConfiguration.getFilesModificationTime()); + Assert.assertEquals("2011-12-03T11:42:05Z", rawConfiguration.getCreationTime()); Assert.assertEquals(Paths.get("test"), rawConfiguration.getDockerExecutable().get()); Assert.assertEquals( new HashMap<>(ImmutableMap.of("docker", "client")), diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java index bb1540c36b..440ae17779 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibExtensionTest.java @@ -28,10 +28,14 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Properties; import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; import org.gradle.testfixtures.ProjectBuilder; import org.junit.Before; import org.junit.Rule; @@ -58,7 +62,7 @@ public void setUp() { @Test public void testFrom() { assertThat(testJibExtension.getFrom().getImage()).isNull(); - assertThat(testJibExtension.getFrom().getCredHelper()).isNull(); + assertThat(testJibExtension.getFrom().getCredHelper().getHelper()).isNull(); List defaultPlatforms = testJibExtension.getFrom().getPlatforms().get(); assertThat(defaultPlatforms).hasSize(1); @@ -72,16 +76,16 @@ public void testFrom() { from.auth(auth -> auth.setUsername("some username")); from.auth(auth -> auth.setPassword("some password")); from.platforms( - platformSpec -> { - platformSpec.platform( - platform -> { - platform.setArchitecture("arm"); - platform.setOs("windows"); - }); - }); + platformSpec -> + platformSpec.platform( + platform -> { + platform.setArchitecture("arm"); + platform.setOs("windows"); + })); }); assertThat(testJibExtension.getFrom().getImage()).isEqualTo("some image"); - assertThat(testJibExtension.getFrom().getCredHelper()).isEqualTo("some cred helper"); + assertThat(testJibExtension.getFrom().getCredHelper().getHelper()) + .isEqualTo("some cred helper"); assertThat(testJibExtension.getFrom().getAuth().getUsername()).isEqualTo("some username"); assertThat(testJibExtension.getFrom().getAuth().getPassword()).isEqualTo("some password"); @@ -91,10 +95,30 @@ public void testFrom() { assertThat(platforms.get(0).getOs()).isEqualTo("windows"); } + @Test + public void testFromCredHelperClosure() { + assertThat(testJibExtension.getFrom().getImage()).isNull(); + assertThat(testJibExtension.getFrom().getCredHelper().getHelper()).isNull(); + + testJibExtension.from( + from -> { + from.setImage("some image"); + from.credHelper( + credHelper -> { + credHelper.setHelper("some cred helper"); + credHelper.setEnvironment(Collections.singletonMap("ENV_VARIABLE", "Value")); + }); + }); + assertThat(testJibExtension.getFrom().getCredHelper().getHelper()) + .isEqualTo("some cred helper"); + assertThat(testJibExtension.getFrom().getCredHelper().getEnvironment()) + .isEqualTo(Collections.singletonMap("ENV_VARIABLE", "Value")); + } + @Test public void testTo() { assertThat(testJibExtension.getTo().getImage()).isNull(); - assertThat(testJibExtension.getTo().getCredHelper()).isNull(); + assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isNull(); testJibExtension.to( to -> { @@ -104,11 +128,30 @@ public void testTo() { to.auth(auth -> auth.setPassword("some password")); }); assertThat(testJibExtension.getTo().getImage()).isEqualTo("some image"); - assertThat(testJibExtension.getTo().getCredHelper()).isEqualTo("some cred helper"); + assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isEqualTo("some cred helper"); assertThat(testJibExtension.getTo().getAuth().getUsername()).isEqualTo("some username"); assertThat(testJibExtension.getTo().getAuth().getPassword()).isEqualTo("some password"); } + @Test + public void testToCredHelperClosure() { + assertThat(testJibExtension.getTo().getImage()).isNull(); + assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isNull(); + + testJibExtension.to( + to -> { + to.setImage("some image"); + to.credHelper( + credHelper -> { + credHelper.setHelper("some cred helper"); + credHelper.setEnvironment(Collections.singletonMap("ENV_VARIABLE", "Value")); + }); + }); + assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isEqualTo("some cred helper"); + assertThat(testJibExtension.getTo().getCredHelper().getEnvironment()) + .isEqualTo(Collections.singletonMap("ENV_VARIABLE", "Value")); + } + @Test public void testToTags_noTagsPropertySet() { assertThat(testJibExtension.getTo().getTags()).isEmpty(); @@ -142,9 +185,9 @@ public void testContainer() { assertThat(testJibExtension.getContainer().getPorts()).isEmpty(); assertThat(testJibExtension.getContainer().getLabels().get()).isEmpty(); assertThat(testJibExtension.getContainer().getAppRoot()).isEmpty(); - assertThat(testJibExtension.getContainer().getFilesModificationTime()) + assertThat(testJibExtension.getContainer().getFilesModificationTime().get()) .isEqualTo("EPOCH_PLUS_SECOND"); - assertThat(testJibExtension.getContainer().getCreationTime()).isEqualTo("EPOCH"); + assertThat(testJibExtension.getContainer().getCreationTime().get()).isEqualTo("EPOCH"); testJibExtension.container( container -> { @@ -158,7 +201,8 @@ public void testContainer() { container.setPorts(Arrays.asList("1000", "2000-2010", "3000")); container.setFormat(ImageFormat.OCI); container.setAppRoot("some invalid appRoot value"); - container.setFilesModificationTime("some invalid time value"); + container.getFilesModificationTime().set("some invalid time value"); + container.getCreationTime().set("some other invalid time value"); }); ContainerParameters container = testJibExtension.getContainer(); assertThat(container.getEntrypoint()).containsExactly("foo", "bar", "baz").inOrder(); @@ -173,7 +217,26 @@ public void testContainer() { assertThat(container.getPorts()).containsExactly("1000", "2000-2010", "3000").inOrder(); assertThat(container.getFormat()).isSameInstanceAs(ImageFormat.OCI); assertThat(container.getAppRoot()).isEqualTo("some invalid appRoot value"); - assertThat(container.getFilesModificationTime()).isEqualTo("some invalid time value"); + assertThat(container.getFilesModificationTime().get()).isEqualTo("some invalid time value"); + assertThat(container.getCreationTime().get()).isEqualTo("some other invalid time value"); + testJibExtension.container( + extensionContainer -> { + extensionContainer.getFilesModificationTime().set((String) null); + extensionContainer.getCreationTime().set((String) null); + }); + container = testJibExtension.getContainer(); + assertThat(container.getFilesModificationTime().get()).isEqualTo("EPOCH_PLUS_SECOND"); + assertThat(container.getCreationTime().get()).isEqualTo("EPOCH"); + } + + @Test + public void testSetFormat() { + testJibExtension.container( + container -> { + container.setFormat("OCI"); + }); + ContainerParameters container = testJibExtension.getContainer(); + assertThat(container.getFormat()).isSameInstanceAs(ImageFormat.OCI); } @Test @@ -191,7 +254,7 @@ public void testExtraDirectories_default() { assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1); assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom()) .isEqualTo(fakeProject.getProjectDir().toPath().resolve("src/main/jib")); - assertThat(testJibExtension.getExtraDirectories().getPermissions()).isEmpty(); + assertThat(testJibExtension.getExtraDirectories().getPermissions().get()).isEmpty(); } @Test @@ -199,15 +262,34 @@ public void testExtraDirectories() { testJibExtension.extraDirectories( extraDirectories -> { extraDirectories.setPaths("test/path"); - extraDirectories.setPermissions(ImmutableMap.of("file1", "123", "file2", "456")); }); assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1); assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom()) .isEqualTo(fakeProject.getProjectDir().toPath().resolve("test/path")); - assertThat(testJibExtension.getExtraDirectories().getPermissions()) - .containsExactly("file1", "123", "file2", "456") - .inOrder(); + } + + @Test + public void testExtraDirectories_lazyEvaluation_setFromInto() { + testJibExtension.extraDirectories( + extraDirectories -> + extraDirectories.paths( + paths -> { + ProviderFactory providerFactory = fakeProject.getProviders(); + Provider from = providerFactory.provider(() -> "test/path"); + Provider into = providerFactory.provider(() -> "/target"); + paths.path( + path -> { + path.setFrom(from); + path.setInto(into); + }); + })); + + assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(1); + assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom()) + .isEqualTo(fakeProject.getProjectDir().toPath().resolve("test/path")); + assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getInto()) + .isEqualTo("/target"); } @Test @@ -260,12 +342,13 @@ public void testExtraDirectories_stringListForPaths() { } @Test - public void testExtraDirectories_fileListForPaths() { + public void testExtraDirectories_lazyEvaluation_StringListForPaths() { testJibExtension.extraDirectories( extraDirectories -> { - extraDirectories.setPaths( - Arrays.asList( - Paths.get("test", "path").toFile(), Paths.get("another", "path").toFile())); + ProviderFactory providerFactory = fakeProject.getProviders(); + Provider paths = + providerFactory.provider(() -> Arrays.asList("test/path", "another/path")); + extraDirectories.setPaths(paths); }); assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2); @@ -275,6 +358,21 @@ public void testExtraDirectories_fileListForPaths() { .isEqualTo(fakeProject.getProjectDir().toPath().resolve("another/path")); } + @Test + public void testExtraDirectories_fileListForPaths() { + testJibExtension.extraDirectories( + extraDirectories -> + extraDirectories.setPaths( + Arrays.asList( + Paths.get("test", "path").toFile(), Paths.get("another", "path").toFile()))); + + assertThat(testJibExtension.getExtraDirectories().getPaths()).hasSize(2); + assertThat(testJibExtension.getExtraDirectories().getPaths().get(0).getFrom()) + .isEqualTo(fakeProject.getProjectDir().toPath().resolve("test/path")); + assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom()) + .isEqualTo(fakeProject.getProjectDir().toPath().resolve("another/path")); + } + @Test public void testDockerClient() { testJibExtension.dockerClient( @@ -339,14 +437,22 @@ public void testProperties() { System.setProperty("jib.from.image", "fromImage"); assertThat(testJibExtension.getFrom().getImage()).isEqualTo("fromImage"); System.setProperty("jib.from.credHelper", "credHelper"); - assertThat(testJibExtension.getFrom().getCredHelper()).isEqualTo("credHelper"); + assertThat(testJibExtension.getFrom().getCredHelper().getHelper()).isEqualTo("credHelper"); + + System.setProperty("jib.from.platforms", "linux/amd64,darwin/arm64"); + List platforms = testJibExtension.getFrom().getPlatforms().get(); + assertThat(platforms).hasSize(2); + assertThat(platforms.get(0).getOs()).isEqualTo("linux"); + assertThat(platforms.get(0).getArchitecture()).isEqualTo("amd64"); + assertThat(platforms.get(1).getOs()).isEqualTo("darwin"); + assertThat(platforms.get(1).getArchitecture()).isEqualTo("arm64"); System.setProperty("jib.to.image", "toImage"); assertThat(testJibExtension.getTo().getImage()).isEqualTo("toImage"); System.setProperty("jib.to.tags", "tag1,tag2,tag3"); assertThat(testJibExtension.getTo().getTags()).containsExactly("tag1", "tag2", "tag3"); System.setProperty("jib.to.credHelper", "credHelper"); - assertThat(testJibExtension.getTo().getCredHelper()).isEqualTo("credHelper"); + assertThat(testJibExtension.getTo().getCredHelper().getHelper()).isEqualTo("credHelper"); System.setProperty("jib.container.appRoot", "appRoot"); assertThat(testJibExtension.getContainer().getAppRoot()).isEqualTo("appRoot"); @@ -387,8 +493,16 @@ public void testProperties() { System.setProperty("jib.container.user", "myUser"); assertThat(testJibExtension.getContainer().getUser()).isEqualTo("myUser"); System.setProperty("jib.container.filesModificationTime", "2011-12-03T22:42:05Z"); - assertThat(testJibExtension.getContainer().getFilesModificationTime()) + testJibExtension + .getContainer() + .getFilesModificationTime() + .set("property should override value"); + assertThat(testJibExtension.getContainer().getFilesModificationTime().get()) .isEqualTo("2011-12-03T22:42:05Z"); + System.setProperty("jib.container.creationTime", "2011-12-03T11:42:05Z"); + testJibExtension.getContainer().getCreationTime().set("property should override value"); + assertThat(testJibExtension.getContainer().getCreationTime().get()) + .isEqualTo("2011-12-03T11:42:05Z"); System.setProperty("jib.containerizingMode", "packaged"); assertThat(testJibExtension.getContainerizingMode()).isEqualTo("packaged"); @@ -399,7 +513,7 @@ public void testProperties() { assertThat(testJibExtension.getExtraDirectories().getPaths().get(1).getFrom()) .isEqualTo(Paths.get("/bar/baz")); System.setProperty("jib.extraDirectories.permissions", "/foo/bar=707,/baz=456"); - assertThat(testJibExtension.getExtraDirectories().getPermissions()) + assertThat(testJibExtension.getExtraDirectories().getPermissions().get()) .containsExactly("/foo/bar", "707", "/baz", "456") .inOrder(); @@ -412,6 +526,28 @@ public void testProperties() { .inOrder(); } + @Test + public void testLazyPropertiesFinalization() { + Property filesModificationTime = + testJibExtension.getContainer().getFilesModificationTime(); + filesModificationTime.set((String) null); + filesModificationTime.finalizeValue(); + System.setProperty("jib.container.filesModificationTime", "EPOCH_PLUS_SECOND"); + assertThat(testJibExtension.getContainer().getFilesModificationTime().get()) + .isEqualTo("EPOCH_PLUS_SECOND"); + Property creationTime = testJibExtension.getContainer().getCreationTime(); + creationTime.set((String) null); + creationTime.finalizeValue(); + System.setProperty("jib.container.creationTime", "EPOCH"); + assertThat(testJibExtension.getContainer().getCreationTime().get()).isEqualTo("EPOCH"); + } + + @Test + public void testSystemPropertiesWithInvalidPlatform() { + System.setProperty("jib.from.platforms", "linux /amd64"); + assertThrows(IllegalArgumentException.class, testJibExtension.getFrom()::getPlatforms); + } + @Test public void testPropertiesOutputPaths() { System.setProperties(new Properties()); @@ -441,10 +577,7 @@ public void testPropertiesOutputPaths() { private TargetImageParameters generateTargetImageParametersWithTags(String... tags) { HashSet set = new HashSet<>(Arrays.asList(tags)); - testJibExtension.to( - to -> { - to.setTags(set); - }); + testJibExtension.to(to -> to.setTags(set)); return testJibExtension.getTo(); } } diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibPluginTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibPluginTest.java index 00467dbfd1..fe2829d0dd 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibPluginTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/JibPluginTest.java @@ -137,7 +137,7 @@ public void testCheckJibVersionInvoked() { // Gradle tests aren't run from a jar and so don't have an identifiable plugin version assertThat(exception) .hasMessageThat() - .isEqualTo("Failed to apply plugin [id 'com.google.cloud.tools.jib']"); + .isEqualTo("Failed to apply plugin 'com.google.cloud.tools.jib'."); assertThat(exception.getCause()) .hasMessageThat() .isEqualTo("Could not determine Jib plugin version"); @@ -368,6 +368,63 @@ public void testLazyEvalForLabels() { "labels contain values [firstkey:updated-first-label, secondKey:updated-second-label]"); } + @Test + public void testLazyEvalForEntryPoint() { + BuildResult showEntrypoint = testProject.build("showentrypoint", "-Djib.console=plain"); + assertThat(showEntrypoint.getOutput()).contains("entrypoint contains updated"); + } + + @Test + public void testLazyEvalForExtraDirectories() { + BuildResult checkExtraDirectories = + testProject.build("check-extra-directories", "-Djib.console=plain"); + assertThat(checkExtraDirectories.getOutput()).contains("[/updated:755]"); + assertThat(checkExtraDirectories.getOutput()).contains("updated-custom-extra-dir"); + } + + @Test + public void testLazyEvalForExtraDirectories_individualPaths() throws IOException { + BuildResult checkExtraDirectories = + testProject.build( + "check-extra-directories", "-b=build-extra-dirs.gradle", "-Djib.console=plain"); + + Path extraDirectoryPath = + testProject + .getProjectRoot() + .resolve("src") + .resolve("main") + .resolve("updated-custom-extra-dir") + .toRealPath(); + assertThat(checkExtraDirectories.getOutput()) + .contains("extraDirectories (from): [" + extraDirectoryPath + "]"); + assertThat(checkExtraDirectories.getOutput()) + .contains("extraDirectories (into): [/updated-custom-into-dir]"); + assertThat(checkExtraDirectories.getOutput()) + .contains("extraDirectories (includes): [[include.txt]]"); + assertThat(checkExtraDirectories.getOutput()) + .contains("extraDirectories (excludes): [[exclude.txt]]"); + } + + @Test + public void testLazyEvalForContainerCreationAndFileModificationTimes() { + BuildResult showTimes = testProject.build("showtimes", "-Djib.console=plain"); + String output = showTimes.getOutput(); + assertThat(output).contains("creationTime=2022-07-19T10:23:42Z"); + assertThat(output).contains("filesModificationTime=2022-07-19T11:23:42Z"); + } + + @Test + public void testLazyEvalForMainClass() { + BuildResult showLabels = testProject.build("showMainClass"); + assertThat(showLabels.getOutput()).contains("mainClass value updated"); + } + + @Test + public void testLazyEvalForJvmFlags() { + BuildResult showLabels = testProject.build("showJvmFlags"); + assertThat(showLabels.getOutput()).contains("jvmFlags value [updated]"); + } + private Project createProject(String... plugins) { Project project = ProjectBuilder.builder().withProjectDir(testProjectRoot.getRoot()).withName("root").build(); diff --git a/jib-gradle-plugin/src/test/resources/gradle/application/extra-directory/foo b/jib-gradle-plugin/src/test/resources/gradle/application/extra-directory/foo new file mode 100644 index 0000000000..85e4ff84b7 --- /dev/null +++ b/jib-gradle-plugin/src/test/resources/gradle/application/extra-directory/foo @@ -0,0 +1 @@ +Unused file for committing extra directory \ No newline at end of file diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/build.gradle index b9c6d8941b..3f84836125 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/complex-service/build.gradle @@ -8,13 +8,13 @@ targetCompatibility = 1.8 repositories { flatDir { - dirs "libs" + dirs 'libs' } } jib { from { - image = "scratch" + image = 'scratch' } extraDirectories { paths = file('src/main/other-jib') @@ -24,13 +24,13 @@ jib { sourceSets { main { resources { - srcDirs "src/main/extra-resources-1", "src/main/extra-resources-2" + srcDirs 'src/main/extra-resources-1', 'src/main/extra-resources-2' } } } dependencies { - compile project(':lib') - compile name: "dependency-1.0.0" - compile name: "dependencyX-1.0.0-SNAPSHOT" + implementation project(':lib') + implementation name: 'dependency-1.0.0' + implementation name: 'dependencyX-1.0.0-SNAPSHOT' } diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/simple-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/simple-service/build.gradle index 56a4b986a5..cbe32f50ee 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/simple-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/all-local-multi-service/simple-service/build.gradle @@ -12,7 +12,7 @@ repositories { jib { from { - image = "scratch" + image = 'scratch' } // only use buildTar -} \ No newline at end of file +} diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build-extra-dirs.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build-extra-dirs.gradle new file mode 100644 index 0000000000..628eb328d4 --- /dev/null +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build-extra-dirs.gradle @@ -0,0 +1,47 @@ +plugins { + id 'java' + id 'com.google.cloud.tools.jib' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + implementation 'com.google.guava:guava:23.6-jre' +} + +project.ext.value = 'original' + +project.afterEvaluate { + project.ext.value = 'updated' + project.ext.getCustomIncludes = { -> return ['include.txt'] } + project.ext.getCustomExcludes = { -> return ['exclude.txt'] } +} + +jib { + extraDirectories { + paths { + path { + from = project.provider { 'src/main/' + project.ext.value + '-custom-extra-dir' } + into = project.provider { '/' + project.ext.value + '-custom-into-dir' } + includes = project.provider { -> project.ext.getCustomIncludes() } + excludes = project.provider { -> project.ext.getCustomExcludes() } + } + } + } +} + +tasks.register('check-extra-directories') { + List from = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['from']} + List into = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['into']} + List includes = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['includes'].get()} + List excludes = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['excludes'].get()} + println('extraDirectories (from): ' + from) + println('extraDirectories (into): ' + into) + println('extraDirectories (includes): ' + includes) + println('extraDirectories (excludes): ' + excludes) +} diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build.gradle index 5ce1b8378f..621baa34b5 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/build.gradle @@ -11,31 +11,74 @@ repositories { } dependencies { - compile 'com.google.guava:guava:23.6-jre' + implementation 'com.google.guava:guava:23.6-jre' } -project.ext.value = "original" +project.ext.value = 'original' +project.ext.jibCreationTime = '1970-01-23T00:23:42Z' +project.ext.jibFilesModificationTime = '1970-01-23T01:23:42Z' project.afterEvaluate { - project.ext.value = "updated" + project.ext.value = 'updated' + project.ext.getCustomPermissions = { -> return ['/updated': '755'] } + project.ext.jibCreationTime = '2022-07-19T10:23:42Z' + project.ext.jibFilesModificationTime = '2022-07-19T11:23:42Z' } jib { to { - image = project.provider { project.ext.value + "-image" } - tags = project.provider { [project.ext.value + "-tag", "tag2"] } + image = project.provider { project.ext.value + '-image' } + tags = project.provider { [project.ext.value + '-tag', 'tag2'] } } container { labels = project.provider { [ - firstkey : project.ext.value + "-first-label", - secondKey: project.ext.value + "-second-label" + firstkey : project.ext.value + '-first-label', + secondKey: project.ext.value + '-second-label' ] } + entrypoint = project.provider { [project.ext.value] } + creationTime = project.provider { project.ext.jibCreationTime } + filesModificationTime = project.provider { project.ext.jibFilesModificationTime } + mainClass = project.provider { project.ext.value } + jvmFlags = project.provider { [project.ext.value] } + } + extraDirectories { + paths = project.provider { ['src/main/' + project.ext.value + '-custom-extra-dir'] } + permissions = project.provider { -> project.ext.getCustomPermissions() } } } tasks.register('showlabels') { - Map prop = project.extensions.getByName("jib")["container"]["labels"].get() - println("labels contain values " + prop) + Map prop = project.extensions.getByName('jib')['container']['labels'].get() + println('labels contain values ' + prop) +} + +tasks.register('showentrypoint') { + List prop = project.extensions.getByName('jib')['container'].getEntrypoint() + println('entrypoint contains ' + prop.join(",")) +} + +tasks.register('check-extra-directories') { + List paths = project.extensions.getByName('jib')['extraDirectories']['paths'].collect{ path -> path['from']} + Map permissions = project.extensions.getByName('jib')['extraDirectories']['permissions'].get() + println('extraDirectories paths: ' + paths) + println('extraDirectories permissions: ' + permissions) +} + +tasks.register('showtimes') { + String prop = project.extensions.jib.container.creationTime.get() + println('creationTime=' + prop) + prop = project.extensions.jib.container.filesModificationTime.get() + println('filesModificationTime=' + prop) +} + +tasks.register('showMainClass') { + String prop = project.extensions.jib.container.mainClass + println('mainClass value ' + prop) +} + +tasks.register('showJvmFlags') { + List prop = project.extensions.jib.container.jvmFlags + println('jvmFlags value ' + prop) } diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/src/main/updated-custom-extra-dir/foo b/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/src/main/updated-custom-extra-dir/foo new file mode 100644 index 0000000000..54315cd5f5 --- /dev/null +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/lazy-evaluation/src/main/updated-custom-extra-dir/foo @@ -0,0 +1 @@ +Unused file for committing parent directory so that it exists for extraDirectories tests \ No newline at end of file diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle index 9b4757ea5a..ee3d6cd81d 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/build.gradle @@ -12,6 +12,6 @@ repositories { jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } -} \ No newline at end of file +} diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle index c4f408f374..596d80f423 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/complex-service/build.gradle @@ -14,13 +14,13 @@ repositories { // -DgroupId=com.google.cloud.tools -DartifactId=tiny-test-lib \ // -Dversion=0.0.1-SNAPSHOT -Dpackaging=jar maven { - url "file:" + project.projectDir + "/local-m2-repo" + url 'file:' + project.projectDir + '/local-m2-repo' } } jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } extraDirectories { paths = file('src/main/other-jib') @@ -30,13 +30,13 @@ jib { sourceSets { main { resources { - srcDirs "src/main/extra-resources-1", "src/main/extra-resources-2" + srcDirs 'src/main/extra-resources-1', 'src/main/extra-resources-2' } } } dependencies { - compile project(':lib') - compile 'org.apache.commons:commons-io:1.3.2' - compile 'com.google.cloud.tools:tiny-test-lib:0.0.1-SNAPSHOT' + implementation project(':lib') + implementation 'org.apache.commons:commons-io:1.3.2' + implementation 'com.google.cloud.tools:tiny-test-lib:0.0.1-SNAPSHOT' } diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle index 9b4757ea5a..ee3d6cd81d 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/multi-service/simple-service/build.gradle @@ -12,6 +12,6 @@ repositories { jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } -} \ No newline at end of file +} diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/platform/service/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/platform/service/build.gradle index dc26f781cc..3ee005b253 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/platform/service/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/platform/service/build.gradle @@ -14,6 +14,6 @@ dependencies { jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } } diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/simple/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/simple/build.gradle index 94bbe1963c..7f2be8ac59 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/simple/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/simple/build.gradle @@ -11,12 +11,12 @@ repositories { } dependencies { - compile files('libs/dependency-1.0.0.jar') + implementation files('libs/dependency-1.0.0.jar') } jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } extraDirectories { paths = file('src/main/custom-extra-dir') diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/build.gradle index 0b62c493c9..d38b9531ba 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/skaffold-config/build.gradle @@ -11,21 +11,21 @@ repositories { } dependencies { - compile files('libs/dependency-1.0.0.jar') + implementation files('libs/dependency-1.0.0.jar') } jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } skaffold { watch { - buildIncludes = project.file("script.gradle") - includes = project.files("other/file.txt") - excludes = "src/main/jib/bar/" + buildIncludes = project.file('script.gradle') + includes = project.files('other/file.txt') + excludes = 'src/main/jib/bar/' } sync { - excludes = ["build/classes/java/main/com/test", "src/main/jib/foo"] + excludes = ['build/classes/java/main/com/test', 'src/main/jib/foo'] } } } diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/build.gradle b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/build.gradle index 45dc2c6710..553267dfb9 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/build.gradle +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/build.gradle @@ -11,13 +11,17 @@ repositories { mavenCentral() } +configurations { + moreLibs +} + dependencies { - compileOnly "javax.servlet:servlet-api:2.5" - implementation "javax.annotation:javax.annotation-api:1.2" + providedCompile 'jakarta.servlet:jakarta.servlet-api:5.0.0' + moreLibs 'jakarta.annotation:jakarta.annotation-api:2.1.0' // random extra JAR } jib { to { - image = System.getProperty("_TARGET_IMAGE") + image = System.getProperty('_TARGET_IMAGE') } } diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom-tomcat.xml b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom-tomcat.xml index 2626a734dc..7d4aecb890 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom-tomcat.xml +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom-tomcat.xml @@ -16,15 +16,15 @@ - javax.servlet - servlet-api - 2.5 + jakarta.servlet + jakarta.servlet-api + 5.0.0 provided - javax.annotation - javax.annotation-api - 1.2 + jakarta.annotation + jakarta.annotation-api + 2.1.0 diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom.xml b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom.xml index c51d357f05..7f3106122d 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom.xml +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/pom.xml @@ -16,15 +16,15 @@ - javax.servlet - servlet-api - 2.5 + jakarta.servlet + jakarta.servlet-api + 5.0.0 provided - javax.annotation - javax.annotation-api - 1.2 + jakarta.annotation + jakarta.annotation-api + 2.1.0 diff --git a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java index 05618c6777..ae65db32f0 100644 --- a/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java +++ b/jib-gradle-plugin/src/test/resources/gradle/projects/war_servlet25/src/main/java/example/HelloWorld.java @@ -16,6 +16,9 @@ package example; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -23,9 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class HelloWorld extends HttpServlet { diff --git a/jib-maven-plugin/CHANGELOG.md b/jib-maven-plugin/CHANGELOG.md index 47d0705cea..9aca1d9f0d 100644 --- a/jib-maven-plugin/CHANGELOG.md +++ b/jib-maven-plugin/CHANGELOG.md @@ -9,6 +9,109 @@ All notable changes to this project will be documented in this file. ### Fixed +## 3.4.4 +- fix: allow pushing images with different arch/os to docker daemon [#4265](https://github.com/GoogleContainerTools/jib/issues/4265) +- fix: address windows deadlock issue when determining docker environment info [#4267](https://github.com/GoogleContainerTools/jib/issues/4265) + +## 3.4.3 + +### Fixed +- 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)) + +## 3.4.2 + +### Changed +- deps: bump org.apache.commons:commons-compress from 1.21 to 1.26.0 ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) + +### Fixed +- fix: set PAX headers to address build reproducibility issue ([#4204](https://github.com/GoogleContainerTools/jib/pull/4204)) +- 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)) + +## 3.4.1 + +### Fixed +- fix: support parsing manifest JSON containing `LayerSources:` from latest Docker. ([#4171](https://github.com/GoogleContainerTools/jib/pull/4171)) + +## 3.4.0 + +### Changed +- deps: bump org.apache.maven:maven-compat from 3.9.1 to 3.9.2. ([#4017](https://github.com/GoogleContainerTools/jib/pull/4017/)) +- 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/)) +- deps: bump com.fasterxml.jackson:jackson-bom from 2.15.0 to 2.15.2. ([#4055](https://github.com/GoogleContainerTools/jib/pull/4055)) +- deps: bump com.google.guava:guava from 32.0.1-jre to 32.1.2-jre ([#4078](https://github.com/GoogleContainerTools/jib/pull/4078)) +- deps: bump org.slf4j:slf4j-simple from 2.0.7 to 2.0.9. ([#4098](https://github.com/GoogleContainerTools/jib/pull/4098)) + +### Fixed +- fix: fix WWW-Authenticate header parsing for Basic authentication ([#4035](https://github.com/GoogleContainerTools/jib/pull/4035/)) + +## 3.3.2 + +### Changed +- Log an info instead of warning when entrypoint makes the image to ignore jvm parameters ([#3904](https://github.com/GoogleContainerTools/jib/pull/3904)) + +Thanks to our community contributors @rmannibucau! + +## 3.3.1 + +### Changed +- Upgraded Google HTTP libraries to 1.42.2 ([#3745](https://github.com/GoogleContainerTools/jib/pull/3745)) + +## 3.3.0 + +### Added + +- 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)) +- Better error messaging when environment map in `container.environment` contains null values ([#3672](https://github.com/GoogleContainerTools/jib/pull/3672)). +- Support for OCI image index manifests ([#3715](https://github.com/GoogleContainerTools/jib/pull/3715)). +- Support for base image layer compressed with zstd ([#3717](https://github.com/GoogleContainerTools/jib/pull/3717)). + +### Changed + +- 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)). +- Upgraded nullaway to 0.9.9. ([#3720](https://github.com/GoogleContainerTools/jib/pull/3720)) +- 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)). +- 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)). + +Thanks to our community contributors @wwadge, @oliver-brm, @rquinio and @gsquared94! + +## 3.2.1 + +### Added + +- Environment variables can now be used in configuring credential helpers. ([#2814](https://github.com/GoogleContainerTools/jib/issues/2814)) + ```xml + + myimage + + ecr-login + + profile + + + + ``` + +### Changed + +- Upgraded jackson-databind to 2.13.2.2 ([#3612](https://github.com/GoogleContainerTools/jib/pull/3612)). + +## 3.2.0 + +### Added + +- [``](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)) +- 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)) + + +### Changed + +- 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)) +- Build will fail if `` contain `from` directory that doesn't exist locally ([#3542](https://github.com/GoogleContainerTools/jib/issues/3542)) + +### Fixed + +- 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)) + ## 3.1.4 ### Changed diff --git a/jib-maven-plugin/README.md b/jib-maven-plugin/README.md index c98badc425..622d6bfc75 100644 --- a/jib-maven-plugin/README.md +++ b/jib-maven-plugin/README.md @@ -32,6 +32,7 @@ For information about the project, see the [Jib project README](../README.md). * [Example](#example) * [Adding Arbitrary Files to the Image](#adding-arbitrary-files-to-the-image) * [Authentication Methods](#authentication-methods) + * [Using Docker configuration files](#using-docker-configuration-files) * [Using Docker Credential Helpers](#using-docker-credential-helpers) * [Using Specific Credentials](#using-specific-credentials) * [Using Maven Settings](#using-maven-settings) @@ -47,7 +48,7 @@ For information about the project, see the [Jib project README](../README.md). You can containerize your application easily with one command: ```shell -mvn compile com.google.cloud.tools:jib-maven-plugin:3.1.4:build -Dimage= +mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.4:build -Dimage= ``` This builds and pushes a container image for your application to a container registry. *If you encounter authentication issues, see [Authentication Methods](#authentication-methods).* @@ -55,7 +56,7 @@ This builds and pushes a container image for your application to a container reg To build to a Docker daemon, use: ```shell -mvn compile com.google.cloud.tools:jib-maven-plugin:3.1.4:dockerBuild +mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.4:dockerBuild ``` If you would like to set up Jib as part of your Maven build, follow the guide below. @@ -73,7 +74,7 @@ In your Maven Java project, add the plugin to your `pom.xml`: com.google.cloud.tools jib-maven-plugin - 3.1.4 + 3.4.4 myimage @@ -133,7 +134,7 @@ For example, to build the image `my-docker-id/my-app`, the configuration would b ``` -#### Using [JFrog Container Registry (JCR)](https://www.jfrog.com/confluence/display/JFROG/JFrog+Container+Registry/) or [JFrog Artifactory](https://www.jfrog.com/confluence/display/JFROG/Getting+Started+with+Artifactory+as+a+Docker+Registry)... +#### 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)... *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.* @@ -232,7 +233,7 @@ mvn package ### Additional Build Artifacts -As 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 `` and `` configuration properties. See [Extended Usage](#outputpaths-object) for more details. +As 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 `` and `` configuration properties. See [Extended Usage](#outputpaths-object) for more details. ## Multi Module Projects @@ -260,16 +261,16 @@ Field | Type | Default | Description Property | Type | Default | Description --- | --- | --- | --- -`image` | string | `adoptopenjdk:{8,11}-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). +`image` | string | `eclipse-temurin:{8,11,17,21}-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). `auth` | [`auth`](#auth-object) | *None* | Specifies credentials directly (alternative to `credHelper`). `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-`). -`platforms` | list | See [`platform`](#platform-object) | _Incubating feature_: Configures platforms of base images to select from a manifest list. +`platforms` | list | See [`platform`](#platform-object) | Configures platforms of base images to select from a manifest list. `to` is an object with the following properties: Property | Type | Default | Description --- | --- | --- | --- -`image` | string | *Required* | The image reference for the target image. This can also be specified via the `-Dimage` command line option. +`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. `auth` | [`auth`](#auth-object) | *None* | Specifies credentials directly (alternative to `credHelper`). `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-`). `tags` | list | *None* | Additional tags to push to. @@ -340,7 +341,7 @@ Property | Type | Default | Description Property | Type | Default | Description --- | --- | --- | --- -`executable` | string | `docker` | Sets the path to the Docker executable that is called to load the image into the Docker daemon. +`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. `environment` | map | *None* | Sets environment variables used by the Docker executable. #### System Properties @@ -375,20 +376,20 @@ Property | Type | Default | Description *\*\*\* The default base image cache is in the following locations on each platform:* * *Linux: `[cache root]/google-cloud-tools-java/jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/.cache/` if not set)* * *Mac: `[cache root]/Google/Jib/`, where `[cache root]` is `$XDG_CACHE_HOME` (`$HOME/Library/Caches/` if not set)* - * *Windows: `[cache root]\Google\Jib\Cache`, where `[cache root]` is `$XDG_CACHE_HOME` (`%LOCALAPPDATA%` if not set)* + * *Windows: `[cache root]\Google\Jib\Cache`, where `[cache root]` is `%XDG_CACHE_HOME%` (`%LOCALAPPDATA%` if not set)* ### Global Jib Configuration Some options can be set in the global Jib configuration file. The file is at the following locations on each platform: * *Linux: `[config root]/google-cloud-tools-java/jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/.config/` if not set)* -* *Mac: `[config root]/Google/Jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/Library/Preferences/Config/` if not set)* -* *Windows: `[config root]\Google\Jib\Config\config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`%LOCALAPPDATA%` if not set)* +* *Mac: `[config root]/Google/Jib/config.json`, where `[config root]` is `$XDG_CONFIG_HOME` (`$HOME/Library/Preferences/` if not set)* +* *Windows: `[config root]\Google\Jib\Config\config.json`, where `[config root]` is `%XDG_CONFIG_HOME%` (`%LOCALAPPDATA%` if not set)* -#### Properties +#### Properties * `disableUpdateCheck`: when set to true, disables the periodic up-to-date version check. -* `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 successfuly pull a base image. +* `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. ```json { @@ -460,8 +461,8 @@ There are three different types of base images that Jib accepts: an image from a Prefix | Example | Type --- | --- | --- -*None* | `adoptopenjdk:11-jre` | Pulls the base image from a registry. -`registry://` | `registry://adoptopenjdk:11-jre` | Pulls the base image from a registry. +*None* | `openjdk:11-jre` | Pulls the base image from a registry. +`registry://` | `registry://eclipse-temurin:11-jre` | Pulls the base image from a registry. `docker://` | `docker://busybox` | Retrieves the base image from the Docker daemon. `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`). @@ -546,7 +547,13 @@ You may also specify the target of the copy and include or exclude files: ### Authentication Methods -Pushing/pulling from private registries require authorization credentials. These can be [retrieved using Docker credential helpers](#using-docker-credential-helpers) or [defined in your Maven settings](#using-maven-settings). If you do not define credentials explicitly, Jib will try to [use credentials defined in your Docker config](/../../issues/101) or infer common credential helpers. +Pushing/pulling from private registries require authorization credentials. + +#### Using Docker configuration files + +* 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`. + +See [this issue](/../../issues/101) and [`man containers-auth.json`](https://www.mankier.com/5/containers-auth.json) for more information about the files. #### Using Docker Credential Helpers diff --git a/jib-maven-plugin/gradle.properties b/jib-maven-plugin/gradle.properties index 3e072365df..f78665a194 100644 --- a/jib-maven-plugin/gradle.properties +++ b/jib-maven-plugin/gradle.properties @@ -1 +1 @@ -version = 3.1.5-SNAPSHOT +version = 3.4.5-SNAPSHOT diff --git a/jib-maven-plugin/kokoro/release_build.sh b/jib-maven-plugin/kokoro/release_build.sh index 92495fabab..185ef98611 100644 --- a/jib-maven-plugin/kokoro/release_build.sh +++ b/jib-maven-plugin/kokoro/release_build.sh @@ -5,5 +5,10 @@ set -o errexit # Display commands to stderr. set -o xtrace +# Append to JAVA_TOOL_OPTIONS to suppress warnings from kokoro container os. +if [ "${KOKORO_JOB_CLUSTER}" = "GCP_UBUNTU_DOCKER" ]; then + JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS} -Xlog:os+container=error" +fi + cd github/jib ./gradlew :jib-maven-plugin:prepareRelease diff --git a/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java b/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java index 422c7e6f5d..d41781d223 100644 --- a/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java +++ b/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java @@ -65,30 +65,24 @@ private static String buildToDockerDaemonAndRun(TestProject project, String imag throws VerificationException, IOException, InterruptedException, DigestException { buildToDockerDaemon(project, imageReference, "pom.xml"); - String dockerInspect = new Command("docker", "inspect", imageReference).run(); + String dockerInspectVolumes = + new Command("docker", "inspect", "-f", "'{{json .Config.Volumes}}'", imageReference).run(); + String dockerInspectExposedPorts = + new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference) + .run(); + String dockerInspectLabels = + new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run(); + String history = new Command("docker", "history", imageReference).run(); + MatcherAssert.assertThat( - dockerInspect, - CoreMatchers.containsString( - " \"Volumes\": {\n" - + " \"/var/log\": {},\n" - + " \"/var/log2\": {}\n" - + " },")); + dockerInspectVolumes, CoreMatchers.containsString("\"/var/log\":{},\"/var/log2\":{}")); MatcherAssert.assertThat( - dockerInspect, + dockerInspectExposedPorts, CoreMatchers.containsString( - " \"ExposedPorts\": {\n" - + " \"1000/tcp\": {},\n" - + " \"2000/udp\": {},\n" - + " \"2001/udp\": {},\n" - + " \"2002/udp\": {},\n" - + " \"2003/udp\": {}")); + "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}")); MatcherAssert.assertThat( - dockerInspect, - CoreMatchers.containsString( - " \"Labels\": {\n" - + " \"key1\": \"value1\",\n" - + " \"key2\": \"value2\"\n" - + " }")); + dockerInspectLabels, + CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\"")); return new Command("docker", "run", "--rm", imageReference).run(); } @@ -261,4 +255,34 @@ public void testExecute_jibRequireVersion_fail() throws IOException { ex.getMessage(), CoreMatchers.containsString("but is required to be [,1.0]")); } } + + @Test + public void testCredHelperConfigurationSimple() + throws DigestException, VerificationException, IOException, InterruptedException { + String targetImage = "simpleimage:maven" + System.nanoTime(); + buildToDockerDaemon(simpleTestProject, targetImage, "pom-cred-helper-1.xml"); + Assert.assertEquals( + "Hello, world. \n1970-01-01T00:00:01Z\n", + new Command("docker", "run", "--rm", targetImage).run()); + } + + @Test + public void testCredHelperConfigurationComplex() + throws DigestException, VerificationException, IOException, InterruptedException { + String targetImage = "simpleimage:maven" + System.nanoTime(); + buildToDockerDaemon(simpleTestProject, targetImage, "pom-cred-helper-2.xml"); + Assert.assertEquals( + "Hello, world. \n1970-01-01T00:00:01Z\n", + new Command("docker", "run", "--rm", targetImage).run()); + } + + @Test + public void testMultiPlatform() + throws DigestException, VerificationException, IOException, InterruptedException { + String targetImage = "multiplatformproject:maven" + System.nanoTime(); + buildToDockerDaemon(simpleTestProject, targetImage, "pom-multiplatform-build.xml"); + Assert.assertEquals( + "Hello, world. \n1970-01-01T00:00:01Z\n", + new Command("docker", "run", "--rm", targetImage).run()); + } } diff --git a/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java b/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java index 107745573f..52675002c2 100644 --- a/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java +++ b/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildImageMojoIntegrationTest.java @@ -23,6 +23,7 @@ import com.google.cloud.tools.jib.IntegrationTestingConfiguration; import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.api.DescriptorDigest; +import com.google.cloud.tools.jib.api.HttpRequestTester; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; import com.google.cloud.tools.jib.api.RegistryException; @@ -53,6 +54,8 @@ import javax.annotation.Nullable; import org.apache.maven.it.VerificationException; import org.apache.maven.it.Verifier; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.After; import org.junit.Assume; import org.junit.Before; @@ -68,6 +71,9 @@ public class BuildImageMojoIntegrationTest { public static final LocalRegistry localRegistry = new LocalRegistry(5000, "testuser", "testpassword"); + private static final String dockerHost = + System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost"; + @ClassRule public static final TestProject simpleTestProject = new TestProject("simple"); @ClassRule public static final TestProject emptyTestProject = new TestProject("empty"); @@ -103,7 +109,7 @@ private static Verifier build( Verifier verifier = new Verifier(projectRoot.toString()); verifier.setSystemProperty("jib.useOnlyProjectCache", "true"); verifier.setSystemProperty("_TARGET_IMAGE", imageReference); - if (imageReference.startsWith("localhost")) { + if (imageReference.startsWith(dockerHost)) { verifier.setSystemProperty("jib.allowInsecureRegistries", "true"); } verifier.setAutoclean(false); @@ -197,7 +203,7 @@ private static String buildAndRunAdditionalTag( verifier.setSystemProperty("jib.useOnlyProjectCache", "true"); verifier.setSystemProperty("_TARGET_IMAGE", imageReference); verifier.setSystemProperty("_ADDITIONAL_TAG", additionalTag); - if (imageReference.startsWith("localhost")) { + if (imageReference.startsWith(dockerHost)) { verifier.setSystemProperty("jib.allowInsecureRegistries", "true"); } verifier.setAutoclean(false); @@ -228,6 +234,7 @@ private static String buildAndRunComplex(String imageReference, String pomFile) throws VerificationException, IOException, InterruptedException { Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString()); verifier.setSystemProperty("jib.useOnlyProjectCache", "true"); + verifier.setSystemProperty("_DOCKER_HOST", dockerHost); verifier.setSystemProperty("_TARGET_IMAGE", imageReference); verifier.setSystemProperty("_TARGET_USERNAME", "testuser"); verifier.setSystemProperty("_TARGET_PASSWORD", "testpassword"); @@ -262,22 +269,20 @@ private static String pullAndRunBuiltImage(String imageReference) private static void assertDockerInspectParameters(String imageReference) throws IOException, InterruptedException { - String dockerInspect = new Command("docker", "inspect", imageReference).run(); - assertThat(dockerInspect) - .contains( - " \"ExposedPorts\": {\n" - + " \"1000/tcp\": {},\n" - + " \"2000/udp\": {},\n" - + " \"2001/udp\": {},\n" - + " \"2002/udp\": {},\n" - + " \"2003/udp\": {}"); - assertThat(dockerInspect) - .contains( - " \"Labels\": {\n" - + " \"key1\": \"value1\",\n" - + " \"key2\": \"value2\"\n" - + " }"); + String dockerInspectExposedPorts = + new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference) + .run(); + String dockerInspectLabels = + new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run(); String history = new Command("docker", "history", imageReference).run(); + + MatcherAssert.assertThat( + dockerInspectExposedPorts, + CoreMatchers.containsString( + "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}")); + MatcherAssert.assertThat( + dockerInspectLabels, + CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\"")); assertThat(history).contains("jib-maven-plugin"); } @@ -335,7 +340,7 @@ public void setUp() throws IOException, InterruptedException { localRegistry.pullAndPushToLocal("gcr.io/distroless/java:latest", "distroless/java"); // Make sure resource file has a consistent value at the beginning of each test - // (testExecute_simple overwrites it) + // (testExecute_simple and testBuild_tarBase overwrite it) Files.write( simpleTestProject .getProjectRoot() @@ -430,11 +435,28 @@ public void testBuild_tarBase() throws IOException, InterruptedException, Verifi + "/simplewithtarbase:maven" + System.nanoTime(); + Instant before = Instant.now(); + + // The target registry these tests push to would already have all the layers cached from before, + // causing this test to fail sometimes with the second build being a bit slower than the first + // build. This file change makes sure that a new layer is always pushed the first time to solve + // this issue. + Files.write( + simpleTestProject + .getProjectRoot() + .resolve("src") + .resolve("main") + .resolve("resources") + .resolve("world"), + before.toString().getBytes(StandardCharsets.UTF_8)); + assertThat( buildAndRunFromLocalBase( simpleTestProject.getProjectRoot(), targetImage, "tar://" + path, true)) .isEqualTo( - "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" + "Hello, " + + before + + ". An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n" + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n"); } @@ -545,7 +567,7 @@ public void testExecute_defaultTarget() throws IOException { @Test public void testExecute_complex() throws IOException, InterruptedException, VerificationException, DigestException { - String targetImage = "localhost:5000/compleximage:maven" + System.nanoTime(); + String targetImage = dockerHost + ":5000/compleximage:maven" + System.nanoTime(); Instant before = Instant.now(); String output = buildAndRunComplex(targetImage, "pom-complex.xml"); assertThat(output) @@ -572,7 +594,7 @@ public void testExecute_complex() @Test public void testExecute_timestampCustom() throws IOException, InterruptedException, VerificationException { - String targetImage = "localhost:5000/simpleimage:maven" + System.nanoTime(); + String targetImage = dockerHost + ":5000/simpleimage:maven" + System.nanoTime(); String pom = "pom-timestamps-custom.xml"; assertThat(buildAndRunComplex(targetImage, pom)) .isEqualTo( @@ -585,7 +607,7 @@ public void testExecute_timestampCustom() @Test public void testExecute_complex_sameFromAndToRegistry() throws IOException, InterruptedException, VerificationException { - String targetImage = "localhost:5000/compleximage:maven" + System.nanoTime(); + String targetImage = dockerHost + ":5000/compleximage:maven" + System.nanoTime(); assertThat(buildAndRunComplex(targetImage, "pom-complex.xml")) .isEqualTo( "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n" @@ -597,7 +619,7 @@ public void testExecute_complex_sameFromAndToRegistry() @Test public void testExecute_complexProperties() throws InterruptedException, VerificationException, IOException { - String targetImage = "localhost:5000/compleximage:maven" + System.nanoTime(); + String targetImage = dockerHost + ":5000/compleximage:maven" + System.nanoTime(); assertThat(buildAndRunComplex(targetImage, "pom-complex-properties.xml")) .isEqualTo( "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrwxr-xr-x\nrwxrwxrwx\nfoo\ncat\n" @@ -618,7 +640,7 @@ public void testExecute_jibContainerizeSkips() throws VerificationException, IOE @Test public void testExecute_jibRequireVersion_ok() throws VerificationException, IOException { - String targetImage = "localhost:5000/simpleimage:maven" + System.nanoTime(); + String targetImage = dockerHost + ":5000/simpleimage:maven" + System.nanoTime(); Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString()); verifier.setSystemProperty("_TARGET_IMAGE", targetImage); @@ -652,14 +674,18 @@ public void testExecute_jibRequireVersion_fail() throws IOException { public void testExecute_jettyServlet25() throws VerificationException, IOException, InterruptedException { buildAndRunWebApp(servlet25Project, "jetty-servlet25:maven", "pom.xml"); - HttpGetVerifier.verifyBody("Hello world", new URL("http://localhost:8080/hello")); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080/hello")); } @Test public void testExecute_tomcatServlet25() throws VerificationException, IOException, InterruptedException { buildAndRunWebApp(servlet25Project, "tomcat-servlet25:maven", "pom-tomcat.xml"); - HttpGetVerifier.verifyBody("Hello world", new URL("http://localhost:8080/hello")); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080/hello")); } @Test @@ -680,13 +706,15 @@ public void testExecute_springBootPackaged() int fileSize = Integer.parseInt(sizeOutput.substring(0, sizeOutput.indexOf(' '))); assertThat(fileSize).isLessThan(3000); // should not be a large fat jar - HttpGetVerifier.verifyBody("Hello world", new URL("http://localhost:8080")); + HttpRequestTester.verifyBody( + "Hello world", + new URL("http://" + HttpRequestTester.fetchDockerHostForHttpRequest() + ":8080")); } @Test public void testExecute_multiPlatformBuild() throws IOException, VerificationException, RegistryException { - String targetImage = "localhost:5000/multiplatform:maven" + System.nanoTime(); + String targetImage = dockerHost + ":5000/multiplatform:maven" + System.nanoTime(); Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString()); verifier.setSystemProperty("_TARGET_IMAGE", targetImage); @@ -703,7 +731,8 @@ public void testExecute_multiPlatformBuild() FailoverHttpClient httpClient = new FailoverHttpClient(true, true, ignored -> {}); RegistryClient registryClient = - RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "multiplatform", httpClient) + RegistryClient.factory( + EventHandlers.NONE, dockerHost + ":5000", "multiplatform", httpClient) .setCredential(Credential.from("testuser", "testpassword")) .newRegistryClient(); registryClient.configureBasicAuth(); @@ -767,7 +796,7 @@ private void buildAndRunWebApp(TestProject project, String label, String pomXml) Verifier verifier = new Verifier(project.getProjectRoot().toString()); verifier.setSystemProperty("jib.useOnlyProjectCache", "true"); verifier.setSystemProperty("_TARGET_IMAGE", targetImage); - if (targetImage.startsWith("localhost")) { + if (targetImage.startsWith(dockerHost)) { verifier.setSystemProperty("jib.allowInsecureRegistries", "true"); } verifier.setAutoclean(false); diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java index 9e79e4982b..d7a739a2a8 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildDockerMojo.java @@ -18,9 +18,10 @@ import com.google.cloud.tools.jib.api.CacheDirectoryCreationException; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; -import com.google.cloud.tools.jib.docker.DockerClient; +import com.google.cloud.tools.jib.docker.CliDockerClient; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException; +import com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException; import com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions; import com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException; import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; @@ -67,8 +68,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { Path dockerExecutable = getDockerClientExecutable(); boolean isDockerInstalled = dockerExecutable == null - ? DockerClient.isDefaultDockerInstalled() - : DockerClient.isDockerInstalled(dockerExecutable); + ? CliDockerClient.isDefaultDockerInstalled() + : CliDockerClient.isDockerInstalled(dockerExecutable); if (!isDockerInstalled) { throw new MojoExecutionException( HelpfulSuggestions.forDockerNotInstalled(HELPFUL_SUGGESTIONS_PREFIX)); @@ -165,6 +166,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { } catch (BuildStepsExecutionException ex) { throw new MojoExecutionException(ex.getMessage(), ex.getCause()); + } catch (ExtraDirectoryNotFoundException ex) { + throw new MojoExecutionException( + " contain \"from\" directory that doesn't exist locally: " + + ex.getPath(), + ex); } finally { tempDirectoryProvider.close(); MojoCommon.finishUpdateChecker(projectProperties, updateCheckFuture); diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java index 8fe9f40e3e..6308b78b33 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java @@ -21,6 +21,7 @@ import com.google.cloud.tools.jib.api.buildplan.ImageFormat; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException; +import com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException; import com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions; import com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException; import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; @@ -179,6 +180,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { } catch (BuildStepsExecutionException ex) { throw new MojoExecutionException(ex.getMessage(), ex.getCause()); + } catch (ExtraDirectoryNotFoundException ex) { + throw new MojoExecutionException( + " contain \"from\" directory that doesn't exist locally: " + + ex.getPath(), + ex); } finally { tempDirectoryProvider.close(); MojoCommon.finishUpdateChecker(projectProperties, updateCheckFuture); diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java index 239506a2c2..0dd8f59576 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildTarMojo.java @@ -20,6 +20,7 @@ import com.google.cloud.tools.jib.api.InvalidImageReferenceException; import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider; import com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException; +import com.google.cloud.tools.jib.plugins.common.ExtraDirectoryNotFoundException; import com.google.cloud.tools.jib.plugins.common.HelpfulSuggestions; import com.google.cloud.tools.jib.plugins.common.IncompatibleBaseImageJavaVersionException; import com.google.cloud.tools.jib.plugins.common.InvalidAppRootException; @@ -157,6 +158,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { } catch (BuildStepsExecutionException ex) { throw new MojoExecutionException(ex.getMessage(), ex.getCause()); + } catch (ExtraDirectoryNotFoundException ex) { + throw new MojoExecutionException( + " contain \"from\" directory that doesn't exist locally: " + + ex.getPath(), + ex); } finally { tempDirectoryProvider.close(); MojoCommon.finishUpdateChecker(projectProperties, updateCheckFuture); diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java index 284f1f8a44..0e897cdaa9 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java @@ -20,6 +20,7 @@ import com.google.cloud.tools.jib.plugins.common.AuthProperty; import com.google.cloud.tools.jib.plugins.common.ConfigurationPropertyValidator; import com.google.cloud.tools.jib.plugins.common.PropertyNames; +import com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtensionConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration; @@ -31,11 +32,14 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; @@ -134,6 +138,17 @@ Optional getMode() { /** Configuration for {@code platform} parameter. */ public static class PlatformParameters implements PlatformConfiguration { + private static PlatformParameters of(String osArchitecture) { + Matcher matcher = Pattern.compile("([^/ ]+)/([^/ ]+)").matcher(osArchitecture); + if (!matcher.matches()) { + throw new IllegalArgumentException("Platform must be of form os/architecture."); + } + PlatformParameters platformParameters = new PlatformParameters(); + platformParameters.os = matcher.group(1); + platformParameters.architecture = matcher.group(2); + return platformParameters; + } + @Nullable @Parameter private String os; @Nullable @Parameter private String architecture; @@ -148,20 +163,56 @@ public Optional getArchitectureName() { } } + /** Configuration for {@code [from|to].credHelper} parameter. */ + public static class CredHelperParameters implements CredHelperConfiguration { + @Nullable @Parameter private String helper; + @Parameter private Map environment = new HashMap<>(); + + @Override + public Optional getHelperName() { + return Optional.ofNullable(helper); + } + + @Override + public Map getEnvironment() { + return environment; + } + + public void setHelper(@Nullable String helper) { + this.helper = helper; + } + + /** + * Default setter for Maven. Makes this syntax possible: + * + *
{@code
+     * 
+     *   
+     *     ...
+     *     ecr-login
+     *     ...
+     *   
+     * 
+     * }
+ * + * @param helper the credential helper + */ + public void set(@Nullable String helper) { + this.helper = helper; + } + } + /** Configuration for {@code from} parameter. */ public static class FromConfiguration { @Nullable @Parameter private String image; - @Nullable @Parameter private String credHelper; + @Parameter private CredHelperParameters credHelper = new CredHelperParameters(); @Parameter private FromAuthConfiguration auth = new FromAuthConfiguration(); @Parameter private List platforms; /** Constructor for defaults. */ public FromConfiguration() { - PlatformParameters platform = new PlatformParameters(); - platform.os = "linux"; - platform.architecture = "amd64"; - platforms = Collections.singletonList(platform); + platforms = Collections.singletonList(PlatformParameters.of("linux/amd64")); } } @@ -170,7 +221,7 @@ public static class ToConfiguration { @Nullable @Parameter private String image; @Parameter private List tags = Collections.emptyList(); - @Nullable @Parameter private String credHelper; + @Parameter private CredHelperParameters credHelper = new CredHelperParameters(); @Parameter private ToAuthConfiguration auth = new ToAuthConfiguration(); public void set(String image) { @@ -354,6 +405,12 @@ protected void checkJibVersion() throws MojoExecutionException { * @return the specified platforms */ List getPlatforms() { + String property = getProperty(PropertyNames.FROM_PLATFORMS); + if (property != null) { + return ConfigurationPropertyValidator.parseListProperty(property).stream() + .map(PlatformParameters::of) + .collect(Collectors.toList()); + } return from.platforms; } @@ -372,15 +429,14 @@ String getBaseImage() { } /** - * Gets the base image credential helper. + * Gets the base image credential helper configuration. * - * @return the configured base image credential helper name + * @return configuration for the base image credential helper */ - @Nullable - String getBaseImageCredentialHelperName() { + CredHelperConfiguration getBaseImageCredHelperConfig() { String property = getProperty(PropertyNames.FROM_CRED_HELPER); if (property != null) { - return property; + from.credHelper.setHelper(property); } return from.credHelper; } @@ -425,15 +481,14 @@ Set getTargetImageAdditionalTags() { } /** - * Gets the target image credential helper. + * Gets the target image credential helper configuration. * - * @return the configured target image credential helper name + * @return configuration for the target image credential helper */ - @Nullable - String getTargetImageCredentialHelperName() { + CredHelperConfiguration getTargetImageCredentialHelperConfig() { String property = getProperty(PropertyNames.TO_CRED_HELPER); if (property != null) { - return property; + to.credHelper.setHelper(property); } return to.credHelper; } diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java index 7158e1e262..7b457951c6 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenProjectProperties.java @@ -356,7 +356,7 @@ Map> classifyDependencies( @Override public List getClassFiles() throws IOException { - return new DirectoryWalker(Paths.get(project.getBuild().getOutputDirectory())).walk().asList(); + return new DirectoryWalker(Paths.get(project.getBuild().getOutputDirectory())).walk(); } @Override diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java index 276b638d34..0d11c86f6f 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java @@ -51,8 +51,8 @@ public AuthProperty getFromAuth() { } @Override - public Optional getFromCredHelper() { - return Optional.ofNullable(jibPluginConfiguration.getBaseImageCredentialHelperName()); + public CredHelperConfiguration getFromCredHelper() { + return jibPluginConfiguration.getBaseImageCredHelperConfig(); } @Override @@ -66,8 +66,8 @@ public AuthProperty getToAuth() { } @Override - public Optional getToCredHelper() { - return Optional.ofNullable(jibPluginConfiguration.getTargetImageCredentialHelperName()); + public CredHelperConfiguration getToCredHelper() { + return jibPluginConfiguration.getTargetImageCredentialHelperConfig(); } @Override diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java index d25da1613c..e6d0b7bdef 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java @@ -16,24 +16,26 @@ package com.google.cloud.tools.jib.maven; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.when; + import com.google.cloud.tools.jib.maven.JibPluginConfiguration.PermissionConfiguration; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.google.cloud.tools.jib.maven.JibPluginConfiguration.PlatformParameters; import java.io.File; import java.nio.file.Paths; import java.util.List; +import java.util.Optional; import java.util.Properties; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Build; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; /** Tests for {@link JibPluginConfiguration}. */ @@ -49,8 +51,8 @@ public class JibPluginConfigurationTest { @Before public void setup() { - Mockito.when(session.getSystemProperties()).thenReturn(sessionProperties); - Mockito.when(build.getDirectory()).thenReturn("/test/directory"); + when(session.getSystemProperties()).thenReturn(sessionProperties); + when(build.getDirectory()).thenReturn("/test/directory"); testPluginConfiguration = new JibPluginConfiguration() { @Override @@ -69,228 +71,262 @@ public Log getLog() { @Test public void testDefaults() { - Assert.assertEquals("linux", testPluginConfiguration.getPlatforms().get(0).getOsName().get()); - Assert.assertEquals( - "amd64", testPluginConfiguration.getPlatforms().get(0).getArchitectureName().get()); - Assert.assertEquals("", testPluginConfiguration.getAppRoot()); - Assert.assertNull(testPluginConfiguration.getWorkingDirectory()); - Assert.assertTrue(testPluginConfiguration.getExtraClasspath().isEmpty()); - Assert.assertFalse(testPluginConfiguration.getExpandClasspathDependencies()); - Assert.assertEquals("exploded", testPluginConfiguration.getContainerizingMode()); - Assert.assertEquals("EPOCH_PLUS_SECOND", testPluginConfiguration.getFilesModificationTime()); - Assert.assertEquals("EPOCH", testPluginConfiguration.getCreationTime()); - Assert.assertTrue(testPluginConfiguration.getInjectedPluginExtensions().isEmpty()); + assertThat(testPluginConfiguration.getPlatforms().get(0).getOsName()).hasValue("linux"); + assertThat(testPluginConfiguration.getPlatforms().get(0).getArchitectureName()) + .hasValue("amd64"); + assertThat(testPluginConfiguration.getAppRoot()).isEmpty(); + assertThat(testPluginConfiguration.getWorkingDirectory()).isNull(); + assertThat(testPluginConfiguration.getExtraClasspath()).isEmpty(); + assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isFalse(); + assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo("exploded"); + assertThat(testPluginConfiguration.getFilesModificationTime()).isEqualTo("EPOCH_PLUS_SECOND"); + assertThat(testPluginConfiguration.getCreationTime()).isEqualTo("EPOCH"); + assertThat(testPluginConfiguration.getInjectedPluginExtensions()).isEmpty(); + assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName()) + .isEqualTo(Optional.empty()); + assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getEnvironment()).isEmpty(); + assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName()) + .isEqualTo(Optional.empty()); + assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getEnvironment()) + .isEmpty(); } @Test public void testSystemProperties() { sessionProperties.put("jib.from.image", "fromImage"); - Assert.assertEquals("fromImage", testPluginConfiguration.getBaseImage()); + assertThat(testPluginConfiguration.getBaseImage()).isEqualTo("fromImage"); sessionProperties.put("jib.from.credHelper", "credHelper"); - Assert.assertEquals("credHelper", testPluginConfiguration.getBaseImageCredentialHelperName()); + assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName().get()) + .isEqualTo("credHelper"); + + sessionProperties.put("jib.from.platforms", "linux/amd64,darwin/arm64"); + List platforms = testPluginConfiguration.getPlatforms(); + assertThat(platforms).hasSize(2); + assertThat(platforms.get(0).getOsName()).hasValue("linux"); + assertThat(platforms.get(0).getArchitectureName()).hasValue("amd64"); + assertThat(platforms.get(1).getOsName()).hasValue("darwin"); + assertThat(platforms.get(1).getArchitectureName()).hasValue("arm64"); sessionProperties.put("image", "toImage"); - Assert.assertEquals("toImage", testPluginConfiguration.getTargetImage()); + assertThat(testPluginConfiguration.getTargetImage()).isEqualTo("toImage"); sessionProperties.remove("image"); sessionProperties.put("jib.to.image", "toImage2"); - Assert.assertEquals("toImage2", testPluginConfiguration.getTargetImage()); + assertThat(testPluginConfiguration.getTargetImage()).isEqualTo("toImage2"); sessionProperties.put("jib.to.tags", "tag1,tag2,tag3"); - Assert.assertEquals( - ImmutableSet.of("tag1", "tag2", "tag3"), - testPluginConfiguration.getTargetImageAdditionalTags()); + assertThat(testPluginConfiguration.getTargetImageAdditionalTags()) + .containsExactly("tag1", "tag2", "tag3"); sessionProperties.put("jib.to.credHelper", "credHelper"); - Assert.assertEquals("credHelper", testPluginConfiguration.getTargetImageCredentialHelperName()); + assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName().get()) + .isEqualTo("credHelper"); sessionProperties.put("jib.container.appRoot", "appRoot"); - Assert.assertEquals("appRoot", testPluginConfiguration.getAppRoot()); + assertThat(testPluginConfiguration.getAppRoot()).isEqualTo("appRoot"); sessionProperties.put("jib.container.args", "arg1,arg2,arg3"); - Assert.assertEquals( - ImmutableList.of("arg1", "arg2", "arg3"), testPluginConfiguration.getArgs()); + assertThat(testPluginConfiguration.getArgs()).containsExactly("arg1", "arg2", "arg3").inOrder(); sessionProperties.put("jib.container.entrypoint", "entry1,entry2,entry3"); - Assert.assertEquals( - ImmutableList.of("entry1", "entry2", "entry3"), testPluginConfiguration.getEntrypoint()); + assertThat(testPluginConfiguration.getEntrypoint()) + .containsExactly("entry1", "entry2", "entry3") + .inOrder(); sessionProperties.put("jib.container.environment", "env1=val1,env2=val2"); - Assert.assertEquals( - ImmutableMap.of("env1", "val1", "env2", "val2"), testPluginConfiguration.getEnvironment()); + assertThat(testPluginConfiguration.getEnvironment()) + .containsExactly("env1", "val1", "env2", "val2") + .inOrder(); sessionProperties.put("jib.container.format", "format"); - Assert.assertEquals("format", testPluginConfiguration.getFormat()); + assertThat(testPluginConfiguration.getFormat()).isEqualTo("format"); sessionProperties.put("jib.container.jvmFlags", "flag1,flag2,flag3"); - Assert.assertEquals( - ImmutableList.of("flag1", "flag2", "flag3"), testPluginConfiguration.getJvmFlags()); + assertThat(testPluginConfiguration.getJvmFlags()) + .containsExactly("flag1", "flag2", "flag3") + .inOrder(); sessionProperties.put("jib.container.labels", "label1=val1,label2=val2"); - Assert.assertEquals( - ImmutableMap.of("label1", "val1", "label2", "val2"), testPluginConfiguration.getLabels()); + assertThat(testPluginConfiguration.getLabels()) + .containsExactly("label1", "val1", "label2", "val2") + .inOrder(); sessionProperties.put("jib.container.mainClass", "main"); - Assert.assertEquals("main", testPluginConfiguration.getMainClass()); + assertThat(testPluginConfiguration.getMainClass()).isEqualTo("main"); sessionProperties.put("jib.container.ports", "port1,port2,port3"); - Assert.assertEquals( - ImmutableList.of("port1", "port2", "port3"), testPluginConfiguration.getExposedPorts()); + assertThat(testPluginConfiguration.getExposedPorts()) + .containsExactly("port1", "port2", "port3") + .inOrder(); + ; sessionProperties.put("jib.container.user", "myUser"); - Assert.assertEquals("myUser", testPluginConfiguration.getUser()); + assertThat(testPluginConfiguration.getUser()).isEqualTo("myUser"); sessionProperties.put("jib.container.workingDirectory", "/working/directory"); - Assert.assertEquals("/working/directory", testPluginConfiguration.getWorkingDirectory()); + assertThat(testPluginConfiguration.getWorkingDirectory()).isEqualTo("/working/directory"); sessionProperties.put("jib.container.filesModificationTime", "2011-12-03T22:42:05Z"); - Assert.assertEquals("2011-12-03T22:42:05Z", testPluginConfiguration.getFilesModificationTime()); + assertThat(testPluginConfiguration.getFilesModificationTime()) + .isEqualTo("2011-12-03T22:42:05Z"); sessionProperties.put("jib.container.creationTime", "2011-12-03T22:42:05Z"); - Assert.assertEquals("2011-12-03T22:42:05Z", testPluginConfiguration.getCreationTime()); + assertThat(testPluginConfiguration.getCreationTime()).isEqualTo("2011-12-03T22:42:05Z"); sessionProperties.put("jib.container.extraClasspath", "/foo,/bar"); - Assert.assertEquals( - ImmutableList.of("/foo", "/bar"), testPluginConfiguration.getExtraClasspath()); + assertThat(testPluginConfiguration.getExtraClasspath()) + .containsExactly("/foo", "/bar") + .inOrder(); sessionProperties.put("jib.container.expandClasspathDependencies", "true"); - Assert.assertTrue(testPluginConfiguration.getExpandClasspathDependencies()); + assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isTrue(); sessionProperties.put("jib.containerizingMode", "packaged"); - Assert.assertEquals("packaged", testPluginConfiguration.getContainerizingMode()); + assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo("packaged"); sessionProperties.put("jib.dockerClient.executable", "test-exec"); - Assert.assertEquals( - Paths.get("test-exec"), testPluginConfiguration.getDockerClientExecutable()); + assertThat(testPluginConfiguration.getDockerClientExecutable()) + .isEqualTo(Paths.get("test-exec")); sessionProperties.put("jib.dockerClient.environment", "env1=val1,env2=val2"); - Assert.assertEquals( - ImmutableMap.of("env1", "val1", "env2", "val2"), - testPluginConfiguration.getDockerClientEnvironment()); + assertThat(testPluginConfiguration.getDockerClientEnvironment()) + .containsExactly("env1", "val1", "env2", "val2") + .inOrder(); + } + + @Test + public void testSystemPropertiesWithInvalidPlatform() { + sessionProperties.put("jib.from.platforms", "linux /amd64"); + assertThrows(IllegalArgumentException.class, testPluginConfiguration::getPlatforms); } @Test public void testSystemPropertiesExtraDirectories() { sessionProperties.put("jib.extraDirectories.paths", "custom-jib"); - Assert.assertEquals(1, testPluginConfiguration.getExtraDirectories().size()); - Assert.assertEquals( - Paths.get("custom-jib"), testPluginConfiguration.getExtraDirectories().get(0).getFrom()); - Assert.assertEquals("/", testPluginConfiguration.getExtraDirectories().get(0).getInto()); + assertThat(testPluginConfiguration.getExtraDirectories()).hasSize(1); + assertThat(testPluginConfiguration.getExtraDirectories().get(0).getFrom()) + .isEqualTo(Paths.get("custom-jib")); + assertThat(testPluginConfiguration.getExtraDirectories().get(0).getInto()).isEqualTo("/"); sessionProperties.put("jib.extraDirectories.permissions", "/test/file1=123,/another/file=456"); List permissions = testPluginConfiguration.getExtraDirectoryPermissions(); - Assert.assertEquals("/test/file1", permissions.get(0).getFile().get()); - Assert.assertEquals("123", permissions.get(0).getMode().get()); - Assert.assertEquals("/another/file", permissions.get(1).getFile().get()); - Assert.assertEquals("456", permissions.get(1).getMode().get()); + assertThat(permissions.get(0).getFile()).hasValue("/test/file1"); + assertThat(permissions.get(0).getMode()).hasValue("123"); + assertThat(permissions.get(1).getFile()).hasValue("/another/file"); + assertThat(permissions.get(1).getMode()).hasValue("456"); } @Test public void testSystemPropertiesOutputPaths() { // Absolute paths sessionProperties.put("jib.outputPaths.digest", "/digest/path"); - Assert.assertEquals(Paths.get("/digest/path"), testPluginConfiguration.getDigestOutputPath()); + assertThat(testPluginConfiguration.getDigestOutputPath()).isEqualTo(Paths.get("/digest/path")); sessionProperties.put("jib.outputPaths.imageId", "/id/path"); - Assert.assertEquals(Paths.get("/id/path"), testPluginConfiguration.getImageIdOutputPath()); + assertThat(testPluginConfiguration.getImageIdOutputPath()).isEqualTo(Paths.get("/id/path")); sessionProperties.put("jib.outputPaths.tar", "/tar/path"); - Assert.assertEquals(Paths.get("/tar/path"), testPluginConfiguration.getTarOutputPath()); + assertThat(testPluginConfiguration.getTarOutputPath()).isEqualTo(Paths.get("/tar/path")); // Relative paths sessionProperties.put("jib.outputPaths.digest", "digest/path"); - Assert.assertEquals( - Paths.get("/repository/project/digest/path"), - testPluginConfiguration.getDigestOutputPath()); + assertThat(testPluginConfiguration.getDigestOutputPath()) + .isEqualTo(Paths.get("/repository/project/digest/path")); sessionProperties.put("jib.outputPaths.imageId", "id/path"); - Assert.assertEquals( - Paths.get("/repository/project/id/path"), testPluginConfiguration.getImageIdOutputPath()); + assertThat(testPluginConfiguration.getImageIdOutputPath()) + .isEqualTo(Paths.get("/repository/project/id/path")); sessionProperties.put("jib.outputPaths.imageJson", "json/path"); - Assert.assertEquals( - Paths.get("/repository/project/json/path"), - testPluginConfiguration.getImageJsonOutputPath()); + assertThat(testPluginConfiguration.getImageJsonOutputPath()) + .isEqualTo(Paths.get("/repository/project/json/path")); sessionProperties.put("jib.outputPaths.tar", "tar/path"); - Assert.assertEquals( - Paths.get("/repository/project/tar/path"), testPluginConfiguration.getTarOutputPath()); + assertThat(testPluginConfiguration.getTarOutputPath()) + .isEqualTo(Paths.get("/repository/project/tar/path")); } @Test public void testPomProperties() { project.getProperties().setProperty("jib.from.image", "fromImage"); - Assert.assertEquals("fromImage", testPluginConfiguration.getBaseImage()); + assertThat(testPluginConfiguration.getBaseImage()).isEqualTo("fromImage"); project.getProperties().setProperty("jib.from.credHelper", "credHelper"); - Assert.assertEquals("credHelper", testPluginConfiguration.getBaseImageCredentialHelperName()); + assertThat(testPluginConfiguration.getBaseImageCredHelperConfig().getHelperName().get()) + .isEqualTo("credHelper"); project.getProperties().setProperty("image", "toImage"); - Assert.assertEquals("toImage", testPluginConfiguration.getTargetImage()); + assertThat(testPluginConfiguration.getTargetImage()).isEqualTo("toImage"); project.getProperties().remove("image"); project.getProperties().setProperty("jib.to.image", "toImage2"); - Assert.assertEquals("toImage2", testPluginConfiguration.getTargetImage()); + assertThat(testPluginConfiguration.getTargetImage()).isEqualTo("toImage2"); project.getProperties().setProperty("jib.to.tags", "tag1,tag2,tag3"); - Assert.assertEquals( - ImmutableSet.of("tag1", "tag2", "tag3"), - testPluginConfiguration.getTargetImageAdditionalTags()); + assertThat(testPluginConfiguration.getTargetImageAdditionalTags()) + .containsExactly("tag1", "tag2", "tag3"); project.getProperties().setProperty("jib.to.credHelper", "credHelper"); - Assert.assertEquals("credHelper", testPluginConfiguration.getTargetImageCredentialHelperName()); + assertThat(testPluginConfiguration.getTargetImageCredentialHelperConfig().getHelperName().get()) + .isEqualTo("credHelper"); project.getProperties().setProperty("jib.container.appRoot", "appRoot"); - Assert.assertEquals("appRoot", testPluginConfiguration.getAppRoot()); + assertThat(testPluginConfiguration.getAppRoot()).isEqualTo("appRoot"); project.getProperties().setProperty("jib.container.args", "arg1,arg2,arg3"); - Assert.assertEquals( - ImmutableList.of("arg1", "arg2", "arg3"), testPluginConfiguration.getArgs()); + assertThat(testPluginConfiguration.getArgs()).containsExactly("arg1", "arg2", "arg3").inOrder(); project.getProperties().setProperty("jib.container.entrypoint", "entry1,entry2,entry3"); - Assert.assertEquals( - ImmutableList.of("entry1", "entry2", "entry3"), testPluginConfiguration.getEntrypoint()); + assertThat(testPluginConfiguration.getEntrypoint()) + .containsExactly("entry1", "entry2", "entry3") + .inOrder(); project.getProperties().setProperty("jib.container.environment", "env1=val1,env2=val2"); - Assert.assertEquals( - ImmutableMap.of("env1", "val1", "env2", "val2"), testPluginConfiguration.getEnvironment()); + assertThat(testPluginConfiguration.getEnvironment()) + .containsExactly("env1", "val1", "env2", "val2") + .inOrder(); project.getProperties().setProperty("jib.container.format", "format"); - Assert.assertEquals("format", testPluginConfiguration.getFormat()); + assertThat(testPluginConfiguration.getFormat()).isEqualTo("format"); project.getProperties().setProperty("jib.container.jvmFlags", "flag1,flag2,flag3"); - Assert.assertEquals( - ImmutableList.of("flag1", "flag2", "flag3"), testPluginConfiguration.getJvmFlags()); + assertThat(testPluginConfiguration.getJvmFlags()) + .containsExactly("flag1", "flag2", "flag3") + .inOrder(); project.getProperties().setProperty("jib.container.labels", "label1=val1,label2=val2"); - Assert.assertEquals( - ImmutableMap.of("label1", "val1", "label2", "val2"), testPluginConfiguration.getLabels()); + assertThat(testPluginConfiguration.getLabels()) + .containsExactly("label1", "val1", "label2", "val2") + .inOrder(); project.getProperties().setProperty("jib.container.mainClass", "main"); - Assert.assertEquals("main", testPluginConfiguration.getMainClass()); + assertThat(testPluginConfiguration.getMainClass()).isEqualTo("main"); project.getProperties().setProperty("jib.container.ports", "port1,port2,port3"); - Assert.assertEquals( - ImmutableList.of("port1", "port2", "port3"), testPluginConfiguration.getExposedPorts()); + assertThat(testPluginConfiguration.getExposedPorts()) + .containsExactly("port1", "port2", "port3") + .inOrder(); project.getProperties().setProperty("jib.container.user", "myUser"); - Assert.assertEquals("myUser", testPluginConfiguration.getUser()); + assertThat(testPluginConfiguration.getUser()).isEqualTo("myUser"); project.getProperties().setProperty("jib.container.workingDirectory", "/working/directory"); - Assert.assertEquals("/working/directory", testPluginConfiguration.getWorkingDirectory()); + assertThat(testPluginConfiguration.getWorkingDirectory()).isEqualTo("/working/directory"); project .getProperties() .setProperty("jib.container.filesModificationTime", "2011-12-03T22:42:05Z"); - Assert.assertEquals("2011-12-03T22:42:05Z", testPluginConfiguration.getFilesModificationTime()); + assertThat(testPluginConfiguration.getFilesModificationTime()) + .isEqualTo("2011-12-03T22:42:05Z"); project.getProperties().setProperty("jib.container.creationTime", "2011-12-03T22:42:05Z"); - Assert.assertEquals("2011-12-03T22:42:05Z", testPluginConfiguration.getCreationTime()); + assertThat(testPluginConfiguration.getCreationTime()).isEqualTo("2011-12-03T22:42:05Z"); project.getProperties().setProperty("jib.container.extraClasspath", "/foo,/bar"); - Assert.assertEquals( - ImmutableList.of("/foo", "/bar"), testPluginConfiguration.getExtraClasspath()); + assertThat(testPluginConfiguration.getExtraClasspath()) + .containsExactly("/foo", "/bar") + .inOrder(); project.getProperties().setProperty("jib.container.expandClasspathDependencies", "true"); - Assert.assertTrue(testPluginConfiguration.getExpandClasspathDependencies()); + assertThat(testPluginConfiguration.getExpandClasspathDependencies()).isTrue(); project.getProperties().setProperty("jib.containerizingMode", "packaged"); - Assert.assertEquals("packaged", testPluginConfiguration.getContainerizingMode()); + assertThat(testPluginConfiguration.getContainerizingMode()).isEqualTo("packaged"); project.getProperties().setProperty("jib.dockerClient.executable", "test-exec"); - Assert.assertEquals( - Paths.get("test-exec"), testPluginConfiguration.getDockerClientExecutable()); + assertThat(testPluginConfiguration.getDockerClientExecutable()) + .isEqualTo(Paths.get("test-exec")); project.getProperties().setProperty("jib.dockerClient.environment", "env1=val1,env2=val2"); - Assert.assertEquals( - ImmutableMap.of("env1", "val1", "env2", "val2"), - testPluginConfiguration.getDockerClientEnvironment()); + assertThat(testPluginConfiguration.getDockerClientEnvironment()) + .containsExactly("env1", "val1", "env2", "val2") + .inOrder(); } @Test public void testPomPropertiesExtraDirectories() { project.getProperties().setProperty("jib.extraDirectories.paths", "custom-jib"); - Assert.assertEquals(1, testPluginConfiguration.getExtraDirectories().size()); - Assert.assertEquals( - Paths.get("custom-jib"), testPluginConfiguration.getExtraDirectories().get(0).getFrom()); - Assert.assertEquals("/", testPluginConfiguration.getExtraDirectories().get(0).getInto()); + assertThat(testPluginConfiguration.getExtraDirectories()).hasSize(1); + assertThat(testPluginConfiguration.getExtraDirectories().get(0).getFrom()) + .isEqualTo(Paths.get("custom-jib")); + assertThat(testPluginConfiguration.getExtraDirectories().get(0).getInto()).isEqualTo("/"); project .getProperties() .setProperty("jib.extraDirectories.permissions", "/test/file1=123,/another/file=456"); List permissions = testPluginConfiguration.getExtraDirectoryPermissions(); - Assert.assertEquals("/test/file1", permissions.get(0).getFile().get()); - Assert.assertEquals("123", permissions.get(0).getMode().get()); - Assert.assertEquals("/another/file", permissions.get(1).getFile().get()); - Assert.assertEquals("456", permissions.get(1).getMode().get()); + assertThat(permissions.get(0).getFile()).hasValue("/test/file1"); + assertThat(permissions.get(0).getMode()).hasValue("123"); + assertThat(permissions.get(1).getFile()).hasValue("/another/file"); + assertThat(permissions.get(1).getMode()).hasValue("456"); } @Test public void testPomPropertiesOutputPaths() { project.getProperties().setProperty("jib.outputPaths.digest", "/digest/path"); - Assert.assertEquals(Paths.get("/digest/path"), testPluginConfiguration.getDigestOutputPath()); + assertThat(testPluginConfiguration.getDigestOutputPath()).isEqualTo(Paths.get("/digest/path")); project.getProperties().setProperty("jib.outputPaths.imageId", "/id/path"); - Assert.assertEquals(Paths.get("/id/path"), testPluginConfiguration.getImageIdOutputPath()); + assertThat(testPluginConfiguration.getImageIdOutputPath()).isEqualTo(Paths.get("/id/path")); project.getProperties().setProperty("jib.outputPaths.imageJson", "/json/path"); - Assert.assertEquals(Paths.get("/json/path"), testPluginConfiguration.getImageJsonOutputPath()); + assertThat(testPluginConfiguration.getImageJsonOutputPath()).isEqualTo(Paths.get("/json/path")); project.getProperties().setProperty("jib.outputPaths.tar", "tar/path"); - Assert.assertEquals( - Paths.get("/repository/project/tar/path"), testPluginConfiguration.getTarOutputPath()); + assertThat(testPluginConfiguration.getTarOutputPath()) + .isEqualTo(Paths.get("/repository/project/tar/path")); } @Test @@ -298,12 +334,11 @@ public void testEmptyOrNullTags() { // https://github.com/GoogleContainerTools/jib/issues/1534 // Maven turns empty tags into null entries, and its possible to have empty tags in jib.to.tags sessionProperties.put("jib.to.tags", "a,,b"); - try { - testPluginConfiguration.getTargetImageAdditionalTags(); - Assert.fail(); - } catch (IllegalArgumentException ex) { - Assert.assertEquals("jib.to.tags has empty tag", ex.getMessage()); - } + Exception ex = + assertThrows( + IllegalArgumentException.class, + () -> testPluginConfiguration.getTargetImageAdditionalTags()); + assertThat(ex.getMessage()).isEqualTo("jib.to.tags has empty tag"); } @Test @@ -311,10 +346,10 @@ public void testIsContainerizable_noProperty() { Properties projectProperties = project.getProperties(); projectProperties.remove("jib.containerize"); - Assert.assertTrue(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isTrue(); projectProperties.setProperty("jib.containerize", ""); - Assert.assertTrue(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isTrue(); } @Test @@ -324,10 +359,10 @@ public void testIsContainerizable_artifactId() { Properties projectProperties = project.getProperties(); projectProperties.setProperty("jib.containerize", ":artifact"); - Assert.assertTrue(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isTrue(); projectProperties.setProperty("jib.containerize", ":artifact2"); - Assert.assertFalse(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isFalse(); } @Test @@ -337,10 +372,10 @@ public void testIsContainerizable_groupAndArtifactId() { Properties projectProperties = project.getProperties(); projectProperties.setProperty("jib.containerize", "group:artifact"); - Assert.assertTrue(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isTrue(); projectProperties.setProperty("jib.containerize", "group:artifact2"); - Assert.assertFalse(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isFalse(); } @Test @@ -350,9 +385,9 @@ public void testIsContainerizable_directory() { Properties projectProperties = project.getProperties(); projectProperties.setProperty("jib.containerize", "project"); - Assert.assertTrue(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isTrue(); projectProperties.setProperty("jib.containerize", "project2"); - Assert.assertFalse(testPluginConfiguration.isContainerizable()); + assertThat(testPluginConfiguration.isContainerizable()).isFalse(); } } diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java index 89f791b1d4..44798046e6 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java @@ -19,12 +19,15 @@ import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.maven.JibPluginConfiguration.FromAuthConfiguration; import com.google.cloud.tools.jib.plugins.common.AuthProperty; +import com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Optional; import org.apache.maven.execution.MavenSession; import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; @@ -64,7 +67,22 @@ public void testGetters() { Mockito.when(jibPluginConfiguration.getAppRoot()).thenReturn("/app/root"); Mockito.when(jibPluginConfiguration.getArgs()).thenReturn(Arrays.asList("--log", "info")); Mockito.when(jibPluginConfiguration.getBaseImage()).thenReturn("openjdk:15"); - Mockito.when(jibPluginConfiguration.getBaseImageCredentialHelperName()).thenReturn("gcr"); + + CredHelperConfiguration baseImageCredHelperConfig = Mockito.mock(CredHelperConfiguration.class); + Mockito.when(baseImageCredHelperConfig.getHelperName()).thenReturn(Optional.of("gcr")); + Mockito.when(baseImageCredHelperConfig.getEnvironment()) + .thenReturn(Collections.singletonMap("ENV_VARIABLE", "Value1")); + Mockito.when(jibPluginConfiguration.getBaseImageCredHelperConfig()) + .thenReturn(baseImageCredHelperConfig); + + CredHelperConfiguration targetImageCredHelperConfig = + Mockito.mock(CredHelperConfiguration.class); + Mockito.when(targetImageCredHelperConfig.getHelperName()).thenReturn(Optional.of("gcr")); + Mockito.when(targetImageCredHelperConfig.getEnvironment()) + .thenReturn(Collections.singletonMap("ENV_VARIABLE", "Value2")); + Mockito.when(jibPluginConfiguration.getTargetImageCredentialHelperConfig()) + .thenReturn(targetImageCredHelperConfig); + Mockito.when(jibPluginConfiguration.getEntrypoint()).thenReturn(Arrays.asList("java", "Main")); Mockito.when(jibPluginConfiguration.getEnvironment()) .thenReturn(new HashMap<>(ImmutableMap.of("currency", "dollar"))); @@ -101,7 +119,14 @@ public void testGetters() { Assert.assertEquals( new HashMap<>(ImmutableMap.of("currency", "dollar")), rawConfiguration.getEnvironment()); Assert.assertEquals("/app/root", rawConfiguration.getAppRoot()); - Assert.assertEquals("gcr", rawConfiguration.getFromCredHelper().get()); + Assert.assertEquals("gcr", rawConfiguration.getFromCredHelper().getHelperName().get()); + Assert.assertEquals( + Collections.singletonMap("ENV_VARIABLE", "Value1"), + rawConfiguration.getFromCredHelper().getEnvironment()); + Assert.assertEquals("gcr", rawConfiguration.getToCredHelper().getHelperName().get()); + Assert.assertEquals( + Collections.singletonMap("ENV_VARIABLE", "Value2"), + rawConfiguration.getToCredHelper().getEnvironment()); Assert.assertEquals("openjdk:15", rawConfiguration.getFromImage().get()); Assert.assertEquals(Arrays.asList("-cp", "."), rawConfiguration.getJvmFlags()); Assert.assertEquals(new HashMap<>(ImmutableMap.of("unit", "cm")), rawConfiguration.getLabels()); diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2Test.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2Test.java index b7b1ac91e6..929d57842a 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2Test.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/FilesMojoV2Test.java @@ -153,7 +153,6 @@ public void testFilesMojo_multiModuleComplexService() throws VerificationExcepti complexServiceRoot.resolve("src/main/resources2").toString(), complexServiceRoot.resolve("src/main/jib1").toString(), complexServiceRoot.resolve("src/main/jib2").toString(), - Paths.get("/").toAbsolutePath().resolve("some/random/absolute/path/jib3").toString(), // this test expects standard .m2 locations Paths.get( System.getProperty("user.home"), diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/InitMojoTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/InitMojoTest.java index 8fb38324b4..dc273aa403 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/InitMojoTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/InitMojoTest.java @@ -63,9 +63,9 @@ private static List getJsons(TestProject project) verifier.verifyErrorFreeLog(); Path logFile = Paths.get(verifier.getBasedir()).resolve(verifier.getLogFileName()); String output = String.join("\n", Files.readAllLines(logFile, StandardCharsets.UTF_8)).trim(); - MatcherAssert.assertThat(output, CoreMatchers.startsWith("BEGIN JIB JSON")); + MatcherAssert.assertThat(output, CoreMatchers.containsString("BEGIN JIB JSON")); - Pattern pattern = Pattern.compile("BEGIN JIB JSON\r?\n(\\{.*})"); + Pattern pattern = Pattern.compile(".*BEGIN JIB JSON\r?\n(\\{.*})"); Matcher matcher = pattern.matcher(output); List jsons = new ArrayList<>(); while (matcher.find()) { diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/PackageGoalsMojoTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/PackageGoalsMojoTest.java index beb0385ea8..dbd80d4e77 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/PackageGoalsMojoTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/PackageGoalsMojoTest.java @@ -52,6 +52,9 @@ private void verifyGoals(Path projectRoot, String profilesString, String... expe verifier.verifyErrorFreeLog(); Path logFile = Paths.get(verifier.getBasedir()).resolve(verifier.getLogFileName()); List log = Files.readAllLines(logFile, StandardCharsets.UTF_8); + if (log.size() != 0 && log.get(0).startsWith("Picked up JAVA_TOOL_OPTIONS:")) { + log.remove(0); + } Assert.assertEquals(Arrays.asList(expectedGoals), log); } diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/SyncMapMojoTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/SyncMapMojoTest.java index 0bbb52230c..31001fe075 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/SyncMapMojoTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/skaffold/SyncMapMojoTest.java @@ -68,6 +68,9 @@ private static String getSyncMapJson(Path projectRoot, String module, String pom throws VerificationException, IOException { Path logFile = runBuild(projectRoot, module, pomXml); List outputLines = Files.readAllLines(logFile, StandardCharsets.UTF_8); + if (outputLines.size() != 0 && outputLines.get(0).startsWith("Picked up JAVA_TOOL_OPTIONS:")) { + outputLines.remove(0); + } Assert.assertEquals(3, outputLines.size()); // we expect ["\n", "", ""] Assert.assertEquals("BEGIN JIB JSON: SYNCMAP/1", outputLines.get(1)); return outputLines.get(2); // this is the JSON output @@ -126,12 +129,20 @@ public void testSyncMapMojo_multiProjectOutput() throws IOException, Verificatio generated.get(1)); List direct = parsed.getDirect(); - Assert.assertEquals(1, direct.size()); + Assert.assertEquals(3, direct.size()); assertFilePaths( m2.resolve( "com/google/cloud/tools/tiny-test-lib/0.0.1-SNAPSHOT/tiny-test-lib-0.0.1-SNAPSHOT.jar"), AbsoluteUnixPath.get("/app/libs/tiny-test-lib-0.0.1-SNAPSHOT.jar"), direct.get(0)); + assertFilePaths( + projectRoot.resolve("complex-service/src/main/jib1/foo"), + AbsoluteUnixPath.get("/foo"), + direct.get(1)); + assertFilePaths( + projectRoot.resolve("complex-service/src/main/jib2/bar"), + AbsoluteUnixPath.get("/bar"), + direct.get(2)); } @Test diff --git a/jib-maven-plugin/src/test/resources/maven/projects/default-target/pom.xml b/jib-maven-plugin/src/test/resources/maven/projects/default-target/pom.xml index 7e31c67d14..7c5ac11fb1 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/default-target/pom.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/default-target/pom.xml @@ -39,6 +39,9 @@ jib-maven-plugin ${jib-maven-plugin.version} + + eclipse-temurin:8-jdk-focal + An argument. diff --git a/jib-maven-plugin/src/test/resources/maven/projects/empty/pom.xml b/jib-maven-plugin/src/test/resources/maven/projects/empty/pom.xml index a6186c51ce..7ed2dd3410 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/empty/pom.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/empty/pom.xml @@ -29,6 +29,9 @@ jib-maven-plugin ${jib-maven-plugin.version} + + eclipse-temurin:11-jdk-focal + ${_TARGET_IMAGE} ${_ADDITIONAL_TAG} diff --git a/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/pom.xml b/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/pom.xml index 193654786e..9333545abb 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/pom.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/pom.xml @@ -22,7 +22,6 @@ src/main/jib1 ${project.basedir}/src/main/jib2 - /some/random/absolute/path/jib3
diff --git a/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib1/foo b/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib1/foo new file mode 100644 index 0000000000..1910281566 --- /dev/null +++ b/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib1/foo @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib2/bar b/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib2/bar new file mode 100644 index 0000000000..5716ca5987 --- /dev/null +++ b/jib-maven-plugin/src/test/resources/maven/projects/multi/complex-service/src/main/jib2/bar @@ -0,0 +1 @@ +bar diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/mock-docker.sh b/jib-maven-plugin/src/test/resources/maven/projects/simple/mock-docker.sh index e4b78a0f4c..84eafa19a8 100755 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/mock-docker.sh +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/mock-docker.sh @@ -1,5 +1,11 @@ #!/bin/bash +if [[ "$1" == "info" ]]; then + # Output the JSON string + echo "{\"OSType\":\"linux\",\"Architecture\":\"x86_64\"}" + exit 0 +fi + # Read stdin to avoid broken pipe cat > /dev/null diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-properties.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-properties.xml index 421392636d..4ac0e2b86e 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-properties.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex-properties.xml @@ -11,8 +11,7 @@ UTF-8 UTF-8 @@PluginVersion@@ - - localhost:5000/distroless/java + ${_DOCKER_HOST}:5000/distroless/java testuser testpassword ${_TARGET_IMAGE} diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex.xml index 54f6d615aa..30dc98fcd9 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-complex.xml @@ -40,7 +40,7 @@ ${jib-maven-plugin.version} - localhost:5000/distroless/java + ${_DOCKER_HOST}:5000/distroless/java testuser testpassword diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-1.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-1.xml new file mode 100644 index 0000000000..3f88368030 --- /dev/null +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-1.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + com.test + my-artifact-id + 1 + + + UTF-8 + UTF-8 + @@PluginVersion@@ + + + + + com.test + dependency + 1.0.0 + system + ${project.basedir}/libs/dependency-1.0.0.jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + eclipse-temurin:8-jdk-focal + + + ${_TARGET_IMAGE} + gcr + + + + + + diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-2.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-2.xml new file mode 100644 index 0000000000..7d8133e332 --- /dev/null +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-cred-helper-2.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + com.test + my-artifact-id + 1 + + + UTF-8 + UTF-8 + @@PluginVersion@@ + + + + + com.test + dependency + 1.0.0 + system + ${project.basedir}/libs/dependency-1.0.0.jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + eclipse-temurin:8-jdk-focal + + + ${_TARGET_IMAGE} + + gcr + + A VAR + + + + + + + + diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-jar-containerization.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-jar-containerization.xml index 165643b677..64d5bd8e50 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-jar-containerization.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-jar-containerization.xml @@ -52,6 +52,9 @@ jib-maven-plugin ${jib-maven-plugin.version} + + eclipse-temurin:11-jdk-focal + ${_TARGET_IMAGE} diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11-incompatible.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11-incompatible.xml index 1db5ed7a4b..63bc430158 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11-incompatible.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11-incompatible.xml @@ -40,7 +40,7 @@ ${jib-maven-plugin.version} - gcr.io/distroless/java + eclipse-temurin:8-jdk-focal ${_TARGET_IMAGE} diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11.xml index be1ed2dc59..cff1d74d53 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-java11.xml @@ -39,6 +39,9 @@ jib-maven-plugin ${jib-maven-plugin.version} + + eclipse-temurin:11-jdk-focal + ${_TARGET_IMAGE} diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml index 76a5966111..3269081f05 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml @@ -41,7 +41,7 @@ ${jib-maven-plugin.version} - busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977 + eclipse-temurin:11 arm64 diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-no-to-image.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-no-to-image.xml index 75bad72779..c814f8141c 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-no-to-image.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-no-to-image.xml @@ -40,6 +40,11 @@ com.google.cloud.tools jib-maven-plugin ${jib-maven-plugin.version} + + + eclipse-temurin:11-jdk-focal + +
diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-timestamps-custom.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-timestamps-custom.xml index 92a9d2bc48..c65d15e53f 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-timestamps-custom.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-timestamps-custom.xml @@ -40,7 +40,7 @@ ${jib-maven-plugin.version} - localhost:5000/distroless/java + ${_DOCKER_HOST}:5000/distroless/java testuser testpassword diff --git a/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom-tomcat.xml b/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom-tomcat.xml index 2626a734dc..dc65ff9dea 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom-tomcat.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom-tomcat.xml @@ -16,15 +16,15 @@ - javax.servlet - servlet-api - 2.5 + jakarta.servlet + jakarta.servlet-api + 5.0.0 provided - javax.annotation - javax.annotation-api - 1.2 + jakarta.annotation + jakarta.annotation-api + 2.1.0 @@ -45,7 +45,7 @@ ${jib-maven-plugin.version} - tomcat:8.5-jre8-alpine + tomcat:10-jre8-temurin-focal ${_TARGET_IMAGE} diff --git a/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom.xml b/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom.xml index c51d357f05..7f3106122d 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom.xml +++ b/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/pom.xml @@ -16,15 +16,15 @@ - javax.servlet - servlet-api - 2.5 + jakarta.servlet + jakarta.servlet-api + 5.0.0 provided - javax.annotation - javax.annotation-api - 1.2 + jakarta.annotation + jakarta.annotation-api + 2.1.0 diff --git a/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/java/example/HelloWorld.java b/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/java/example/HelloWorld.java index 05618c6777..ae65db32f0 100644 --- a/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/java/example/HelloWorld.java +++ b/jib-maven-plugin/src/test/resources/maven/projects/war_servlet25/src/main/java/example/HelloWorld.java @@ -16,6 +16,9 @@ package example; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -23,9 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; public class HelloWorld extends HttpServlet { diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidator.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidator.java index b0a0192c13..219dc29b80 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidator.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidator.java @@ -158,18 +158,23 @@ public static Map parseMapProperty(String property) { */ public static List parseListProperty(String property) { List items = new ArrayList<>(); - int startIndex = 0; - for (int endIndex = 0; endIndex < property.length(); endIndex++) { - if (property.charAt(endIndex) == ',') { + StringBuilder token = new StringBuilder(); + for (int i = 0; i < property.length(); i++) { + if (property.charAt(i) == ',') { // Split on non-escaped comma - items.add(property.substring(startIndex, endIndex)); - startIndex = endIndex + 1; - } else if (property.charAt(endIndex) == '\\') { - // Found a backslash, ignore next character - endIndex++; + items.add(token.toString()); + token.setLength(0); + } else { + if (i + 1 < property.length() + && property.charAt(i) == '\\' + && property.charAt(i + 1) == ',') { + // Found an escaped comma. Add a comma. + i++; + } + token.append(property.charAt(i)); } } - items.add(property.substring(startIndex)); + items.add(token.toString()); return items; } diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrievers.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrievers.java index 64200692c0..e3649e54a0 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrievers.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrievers.java @@ -26,10 +26,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.Set; import javax.annotation.Nullable; /** @@ -41,6 +44,12 @@ *
  • {@link CredentialRetrieverFactory#known} for known credential, if set *
  • {@link CredentialRetrieverFactory#dockerCredentialHelper} for a known credential helper, if * set + *
  • {@link CredentialRetrieverFactory#dockerConfig} for {@code + * $XDG_RUNTIME_DIR/containers/auth.json}, + *
  • {@link CredentialRetrieverFactory#dockerConfig} for {@code + * $XDG_CONFIG_HOME/containers/auth.json}, + *
  • {@link CredentialRetrieverFactory#dockerConfig} for {@code + * $HOME/.config/containers/auth.json}, *
  • {@link CredentialRetrieverFactory#known} for known inferred credential, if set *
  • {@link CredentialRetrieverFactory#dockerConfig} for {@code $DOCKER_CONFIG/config.json}, * {@code $DOCKER_CONFIG/.dockerconfigjson}, {@code $DOCKER_CONFIG/.dockercfg}, @@ -60,10 +69,11 @@ public class DefaultCredentialRetrievers { * href="https://docs.docker.com/engine/reference/commandline/login/#privileged-user-requirement">https://docs.docker.com/engine/reference/commandline/login/#privileged-user-requirement. */ private static final Path DOCKER_CONFIG_FILE = Paths.get("config.json"); - // For Kubernetes: https://github.com/GoogleContainerTools/jib/issues/2260 + // for Kubernetes: https://github.com/GoogleContainerTools/jib/issues/2260 private static final Path KUBERNETES_DOCKER_CONFIG_FILE = Paths.get(".dockerconfigjson"); private static final Path LEGACY_DOCKER_CONFIG_FILE = Paths.get(".dockercfg"); - private static final Path DOCKER_DIRECTORY = Paths.get(".docker"); + // for Podman: https://www.mankier.com/5/containers-auth.json + private static final Path XDG_AUTH_FILE = Paths.get("containers").resolve("auth.json"); /** * Creates a new {@link DefaultCredentialRetrievers} with a given {@link @@ -169,47 +179,58 @@ public List asList() throws FileNotFoundException { credentialRetrieverFactory.dockerCredentialHelper("docker-credential-" + suffix)); } } + if (inferredCredentialRetriever != null) { credentialRetrievers.add(inferredCredentialRetriever); } - List checkedDockerDirs = new ArrayList<>(); - String dockerConfigEnv = environment.get("DOCKER_CONFIG"); - if (dockerConfigEnv != null) { - Path dockerConfigEnvPath = Paths.get(dockerConfigEnv); - addDockerFiles(credentialRetrievers, Paths.get(dockerConfigEnv)); - checkedDockerDirs.add(dockerConfigEnvPath); - } + Set dockerConfigFiles = new LinkedHashSet<>(); + String xdgRuntime = environment.get("XDG_RUNTIME_DIR"); + if (xdgRuntime != null) { + dockerConfigFiles.add(Paths.get(xdgRuntime).resolve(XDG_AUTH_FILE)); + } + String xdgConfigHome = environment.get("XDG_CONFIG_HOME"); + if (xdgConfigHome != null) { + dockerConfigFiles.add(Paths.get(xdgConfigHome).resolve(XDG_AUTH_FILE)); + } String homeProperty = systemProperties.getProperty("user.home"); if (homeProperty != null) { - Path homePropertyPath = Paths.get(homeProperty).resolve(DOCKER_DIRECTORY); - if (!checkedDockerDirs.contains(homePropertyPath)) { - addDockerFiles(credentialRetrievers, homePropertyPath); - checkedDockerDirs.add(homePropertyPath); - } + dockerConfigFiles.add(Paths.get(homeProperty).resolve(".config").resolve(XDG_AUTH_FILE)); } - String homeEnvVar = environment.get("HOME"); if (homeEnvVar != null) { - Path homeEnvDockerPath = Paths.get(homeEnvVar).resolve(DOCKER_DIRECTORY); - if (!checkedDockerDirs.contains(homeEnvDockerPath)) { - addDockerFiles(credentialRetrievers, homeEnvDockerPath); - } + dockerConfigFiles.add(Paths.get(homeEnvVar).resolve(".config").resolve(XDG_AUTH_FILE)); } + String dockerConfigEnv = environment.get("DOCKER_CONFIG"); + if (dockerConfigEnv != null) { + dockerConfigFiles.addAll(getDockerFiles(Paths.get(dockerConfigEnv))); + } + if (homeProperty != null) { + dockerConfigFiles.addAll(getDockerFiles(Paths.get(homeProperty).resolve(".docker"))); + } + if (homeEnvVar != null) { + dockerConfigFiles.addAll(getDockerFiles(Paths.get(homeEnvVar).resolve(".docker"))); + } + + dockerConfigFiles.stream() + .map( + path -> + path.endsWith(LEGACY_DOCKER_CONFIG_FILE) + ? credentialRetrieverFactory.legacyDockerConfig(path) + : credentialRetrieverFactory.dockerConfig(path)) + .forEach(credentialRetrievers::add); + credentialRetrievers.add(credentialRetrieverFactory.wellKnownCredentialHelpers()); credentialRetrievers.add(credentialRetrieverFactory.googleApplicationDefaultCredentials()); return credentialRetrievers; } - private void addDockerFiles(List credentialRetrievers, Path configDir) { - credentialRetrievers.add( - credentialRetrieverFactory.dockerConfig(configDir.resolve(DOCKER_CONFIG_FILE))); - credentialRetrievers.add( - credentialRetrieverFactory.dockerConfig(configDir.resolve(KUBERNETES_DOCKER_CONFIG_FILE))); - credentialRetrievers.add( - credentialRetrieverFactory.legacyDockerConfig( - configDir.resolve(LEGACY_DOCKER_CONFIG_FILE))); + private List getDockerFiles(Path configDir) { + return Arrays.asList( + configDir.resolve(DOCKER_CONFIG_FILE), + configDir.resolve(KUBERNETES_DOCKER_CONFIG_FILE), + configDir.resolve(LEGACY_DOCKER_CONFIG_FILE)); } } diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ExtraDirectoryNotFoundException.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ExtraDirectoryNotFoundException.java new file mode 100644 index 0000000000..5c2aac0d88 --- /dev/null +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ExtraDirectoryNotFoundException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.plugins.common; + +/** Indicates that the {@code extraDirectories.paths.path} is not found. */ +public class ExtraDirectoryNotFoundException extends Exception { + + private final String path; + + public ExtraDirectoryNotFoundException(String message, String path) { + super(message); + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java index 026ebe1b06..17b3618558 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java @@ -46,17 +46,20 @@ public class ImageMetadataOutput implements JsonTemplate { private final String imageId; private final String imageDigest; private final List tags; + private final Boolean imagePushed; @JsonCreator ImageMetadataOutput( @JsonProperty(value = "image", required = true) String image, @JsonProperty(value = "imageId", required = true) String imageId, @JsonProperty(value = "imageDigest", required = true) String imageDigest, - @JsonProperty(value = "tags", required = true) List tags) { + @JsonProperty(value = "tags", required = true) List tags, + @JsonProperty(value = "imagePushed", required = true) Boolean imagePushed) { this.image = image; this.imageId = imageId; this.imageDigest = imageDigest; this.tags = tags; + this.imagePushed = imagePushed; } @VisibleForTesting @@ -74,11 +77,12 @@ public static ImageMetadataOutput fromJibContainer(JibContainer jibContainer) { String image = jibContainer.getTargetImage().toString(); String imageId = jibContainer.getImageId().toString(); String imageDigest = jibContainer.getDigest().toString(); + Boolean imagePushed = jibContainer.isImagePushed(); // Make sure tags always appear in a predictable way, by sorting them into a list List tags = ImmutableList.sortedCopyOf(jibContainer.getTags()); - return new ImageMetadataOutput(image, imageId, imageDigest, tags); + return new ImageMetadataOutput(image, imageId, imageDigest, tags, imagePushed); } public String getImage() { @@ -97,6 +101,10 @@ public List getTags() { return tags; } + public Boolean isImagePushed() { + return imagePushed; + } + public String toJson() throws IOException { return JsonTemplateMapper.toUtf8String(this); } diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/IncompatibleBaseImageJavaVersionException.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/IncompatibleBaseImageJavaVersionException.java index 2954d2c3c1..a0b8612e48 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/IncompatibleBaseImageJavaVersionException.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/IncompatibleBaseImageJavaVersionException.java @@ -19,7 +19,7 @@ /** * Exception when the Java version in the base image is incompatible with the Java version of the * application to be containerized. For example, when the project is Java 11 but the base image is - * {@code adoptopenjdk:8-jre}. + * Java 8. */ public class IncompatibleBaseImageJavaVersionException extends Exception { diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java index 73db6ba82a..8ac4001db5 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java @@ -37,6 +37,7 @@ import com.google.cloud.tools.jib.api.buildplan.Platform; import com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory; import com.google.cloud.tools.jib.global.JibSystemProperties; +import com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration; import com.google.cloud.tools.jib.plugins.common.globalconfig.GlobalConfig; @@ -92,6 +93,7 @@ public class PluginConfigurationProcessor { private static final String JIB_CLASSPATH_FILE = "jib-classpath-file"; private static final String JIB_MAIN_CLASS_FILE = "jib-main-class-file"; + private static final Path DEFAULT_JIB_DIR = Paths.get("src").resolve("main").resolve("jib"); private PluginConfigurationProcessor() {} @@ -122,6 +124,8 @@ private PluginConfigurationProcessor() {} * parsed * @throws InvalidCreationTimeException if configured creation time could not be parsed * @throws JibPluginExtensionException if an error occurred while running plugin extensions + * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not + * found */ public static JibBuildRunner createJibBuildRunnerForDockerDaemonImage( RawConfiguration rawConfiguration, @@ -134,7 +138,7 @@ public static JibBuildRunner createJibBuildRunnerForDockerDaemonImage( InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, InvalidFilesModificationTimeException, InvalidCreationTimeException, - JibPluginExtensionException { + ExtraDirectoryNotFoundException, JibPluginExtensionException { ImageReference targetImageReference = getGeneratedTargetDockerTag(rawConfiguration, projectProperties, helpfulSuggestions); DockerDaemonImage targetImage = DockerDaemonImage.named(targetImageReference); @@ -194,6 +198,8 @@ public static JibBuildRunner createJibBuildRunnerForDockerDaemonImage( * parsed * @throws InvalidCreationTimeException if configured creation time could not be parsed * @throws JibPluginExtensionException if an error occurred while running plugin extensions + * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not + * found */ public static JibBuildRunner createJibBuildRunnerForTarImage( RawConfiguration rawConfiguration, @@ -206,7 +212,7 @@ public static JibBuildRunner createJibBuildRunnerForTarImage( InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, InvalidFilesModificationTimeException, InvalidCreationTimeException, - JibPluginExtensionException { + JibPluginExtensionException, ExtraDirectoryNotFoundException { ImageReference targetImageReference = getGeneratedTargetDockerTag(rawConfiguration, projectProperties, helpfulSuggestions); TarImage targetImage = @@ -260,6 +266,8 @@ public static JibBuildRunner createJibBuildRunnerForTarImage( * parsed * @throws InvalidCreationTimeException if configured creation time could not be parsed * @throws JibPluginExtensionException if an error occurred while running plugin extensions + * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not + * found */ public static JibBuildRunner createJibBuildRunnerForRegistryImage( RawConfiguration rawConfiguration, @@ -272,7 +280,7 @@ public static JibBuildRunner createJibBuildRunnerForRegistryImage( InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, InvalidFilesModificationTimeException, InvalidCreationTimeException, - JibPluginExtensionException { + JibPluginExtensionException, ExtraDirectoryNotFoundException { Optional image = rawConfiguration.getToImage(); Preconditions.checkArgument(image.isPresent()); @@ -288,7 +296,7 @@ public static JibBuildRunner createJibBuildRunnerForRegistryImage( PropertyNames.TO_AUTH_PASSWORD, rawConfiguration.getToAuth(), inferredAuthProvider, - rawConfiguration.getToCredHelper().orElse(null)); + rawConfiguration.getToCredHelper()); boolean alwaysCacheBaseImage = Boolean.parseBoolean( @@ -340,6 +348,8 @@ public static JibBuildRunner createJibBuildRunnerForRegistryImage( * @throws InvalidFilesModificationTimeException if configured modification time could not be * parsed * @throws InvalidCreationTimeException if configured creation time could not be parsed + * @throws ExtraDirectoryNotFoundException if the extra directory specified for the build is not + * found */ public static String getSkaffoldSyncMap( RawConfiguration rawConfiguration, ProjectProperties projectProperties, Set excludes) @@ -347,7 +357,7 @@ public static String getSkaffoldSyncMap( IncompatibleBaseImageJavaVersionException, InvalidPlatformException, InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidFilesModificationTimeException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, ExtraDirectoryNotFoundException { JibContainerBuilder jibContainerBuilder = processCommonConfiguration( rawConfiguration, ignored -> Optional.empty(), projectProperties); @@ -406,7 +416,7 @@ static JibContainerBuilder processCommonConfiguration( IncompatibleBaseImageJavaVersionException, IOException, InvalidImageReferenceException, InvalidContainerizingModeException, MainClassInferenceException, InvalidPlatformException, InvalidContainerVolumeException, InvalidWorkingDirectoryException, - InvalidCreationTimeException { + InvalidCreationTimeException, ExtraDirectoryNotFoundException { // Create and configure JibContainerBuilder ModificationTimeProvider modificationTimeProvider = @@ -446,6 +456,8 @@ static JibContainerBuilder processCommonConfiguration( extraDirectory.getExcludesList(), rawConfiguration.getExtraDirectoryPermissions(), modificationTimeProvider)); + } else if (!from.endsWith(DEFAULT_JIB_DIR)) { + throw new ExtraDirectoryNotFoundException(from.toString(), from.toString()); } } return jibContainerBuilder; @@ -461,7 +473,8 @@ static JibContainerBuilder processCommonConfiguration( IOException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { JibSystemProperties.checkHttpTimeoutProperty(); JibSystemProperties.checkProxyPortProperty(); @@ -514,6 +527,12 @@ static JavaContainerBuilder getJavaContainerBuilderWithBaseImage( if (isKnownJava11Image(prefixRemoved) && javaVersion > 11) { throw new IncompatibleBaseImageJavaVersionException(11, javaVersion); } + if (isKnownJava17Image(prefixRemoved) && javaVersion > 17) { + throw new IncompatibleBaseImageJavaVersionException(17, javaVersion); + } + if (isKnownJava21Image(prefixRemoved) && javaVersion > 21) { + throw new IncompatibleBaseImageJavaVersionException(21, javaVersion); + } ImageReference baseImageReference = ImageReference.parse(prefixRemoved); if (baseImageConfig.startsWith(Jib.DOCKER_DAEMON_IMAGE_PREFIX)) { @@ -537,7 +556,7 @@ static JavaContainerBuilder getJavaContainerBuilderWithBaseImage( PropertyNames.FROM_AUTH_PASSWORD, rawConfiguration.getFromAuth(), inferredAuthProvider, - rawConfiguration.getFromCredHelper().orElse(null)); + rawConfiguration.getFromCredHelper()); return JavaContainerBuilder.from(baseImage); } @@ -550,7 +569,8 @@ static JavaContainerBuilder getJavaContainerBuilderWithBaseImage( *
  • null (inheriting from the base image), if the user specified value is {@code INHERIT} *
  • the user specified one, if set *
  • for a WAR project, null (inheriting) if a custom base image is specified, and {@code - * ["java", "-jar", "/usr/local/jetty/start.jar"]} otherwise (default Jetty base image) + * ["java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy"]} otherwise + * (default Jetty base image) *
  • for a non-WAR project, by resolving the main class * * @@ -580,7 +600,7 @@ static List computeEntrypoint( || !rawExtraClasspath.isEmpty() || rawConfiguration.getExpandClasspathDependencies())) { projectProperties.log( - LogEvent.warn( + LogEvent.info( "mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored " + "when entrypoint is specified")); } @@ -603,7 +623,7 @@ static List computeEntrypoint( } return rawConfiguration.getFromImage().isPresent() ? null // Inherit if a custom base image. - : Arrays.asList("java", "-jar", "/usr/local/jetty/start.jar"); + : Arrays.asList("java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy"); } List classpath = new ArrayList<>(rawExtraClasspath); @@ -736,8 +756,7 @@ static void writeFileConservatively(Path file, String content) throws IOExceptio /** * Gets the suitable value for the base image. If the raw base image parameter is null, returns - * {@code "jetty"} for WAR projects or {@code "adoptopenjdk:8-jre"} or {@code - * "adoptopenjdk:11-jre"} for non-WAR. + * {@code "jetty"} for WAR projects, or {@code "eclipse-temurin:{8|11|17}-jre"} for non-WAR. * * @param projectProperties used for providing additional information * @return the base image @@ -752,11 +771,15 @@ static String getDefaultBaseImage(ProjectProperties projectProperties) } int javaVersion = projectProperties.getMajorJavaVersion(); if (javaVersion <= 8) { - return "adoptopenjdk:8-jre"; + return "eclipse-temurin:8-jre"; } else if (javaVersion <= 11) { - return "adoptopenjdk:11-jre"; + return "eclipse-temurin:11-jre"; + } else if (javaVersion <= 17) { + return "eclipse-temurin:17-jre"; + } else if (javaVersion <= 21) { + return "eclipse-temurin:21-jre"; } - throw new IncompatibleBaseImageJavaVersionException(11, javaVersion); + throw new IncompatibleBaseImageJavaVersionException(21, javaVersion); } /** @@ -926,7 +949,7 @@ static Instant getCreationTime(String configuredCreationTime, ProjectProperties case "USE_CURRENT_TIMESTAMP": projectProperties.log( - LogEvent.warn( + LogEvent.debug( "Setting image creation time to current time; your image may not be reproducible.")); return Instant.now(); @@ -956,11 +979,12 @@ private static void configureCredentialRetrievers( String passwordPropertyName, AuthProperty rawAuthConfiguration, InferredAuthProvider inferredAuthProvider, - @Nullable String credHelper) + CredHelperConfiguration credHelperConfiguration) throws FileNotFoundException { DefaultCredentialRetrievers defaultCredentialRetrievers = DefaultCredentialRetrievers.init( - CredentialRetrieverFactory.forImage(imageReference, projectProperties::log)); + CredentialRetrieverFactory.forImage( + imageReference, projectProperties::log, credHelperConfiguration.getEnvironment())); Optional optionalCredential = ConfigurationPropertyValidator.getImageCredential( projectProperties::log, @@ -987,7 +1011,8 @@ private static void configureCredentialRetrievers( } } - defaultCredentialRetrievers.setCredentialHelper(credHelper); + defaultCredentialRetrievers.setCredentialHelper( + credHelperConfiguration.getHelperName().orElse(null)); defaultCredentialRetrievers.asList().forEach(registryImage::addCredentialRetriever); } @@ -1054,7 +1079,8 @@ private static Path getCheckedCacheDirectory(String property, Path defaultPath) * @return {@code true} if the image is a known Java 8 image */ private static boolean isKnownJava8Image(String imageReference) { - return imageReference.startsWith("adoptopenjdk:8"); + return imageReference.startsWith("adoptopenjdk:8") + || imageReference.startsWith("eclipse-temurin:8"); } /** @@ -1064,6 +1090,27 @@ private static boolean isKnownJava8Image(String imageReference) { * @return {@code true} if the image is a known Java 11 image */ private static boolean isKnownJava11Image(String imageReference) { - return imageReference.startsWith("adoptopenjdk:11"); + return imageReference.startsWith("adoptopenjdk:11") + || imageReference.startsWith("eclipse-temurin:11"); + } + + /** + * Checks if the given image is a known Java 17 image. May return false negative. + * + * @param imageReference the image reference + * @return {@code true} if the image is a known Java 17 image + */ + private static boolean isKnownJava17Image(String imageReference) { + return imageReference.startsWith("eclipse-temurin:17"); + } + + /** + * Checks if the given image is a known Java 21 image. May return false negative. + * + * @param imageReference the image reference + * @return {@code true} if the image is a known Java 21 image + */ + private static boolean isKnownJava21Image(String imageReference) { + return imageReference.startsWith("eclipse-temurin:21"); } } diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java index 29ddadbce2..14e27c2ed3 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java @@ -23,6 +23,7 @@ public class PropertyNames { public static final String FROM_CRED_HELPER = "jib.from.credHelper"; public static final String FROM_AUTH_USERNAME = "jib.from.auth.username"; public static final String FROM_AUTH_PASSWORD = "jib.from.auth.password"; + public static final String FROM_PLATFORMS = "jib.from.platforms"; public static final String TO_IMAGE = "jib.to.image"; public static final String TO_IMAGE_ALTERNATE = "image"; public static final String TO_TAGS = "jib.to.tags"; diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java index b3ea91cefd..b31cf326ec 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java @@ -30,7 +30,7 @@ */ public interface RawConfiguration { - static interface ExtensionConfiguration { + interface ExtensionConfiguration { String getExtensionClass(); @@ -39,14 +39,14 @@ static interface ExtensionConfiguration { Optional getExtraConfiguration(); } - static interface PlatformConfiguration { + interface PlatformConfiguration { Optional getOsName(); Optional getArchitectureName(); } - static interface ExtraDirectoriesConfiguration { + interface ExtraDirectoriesConfiguration { Path getFrom(); @@ -57,6 +57,12 @@ static interface ExtraDirectoriesConfiguration { List getExcludesList(); } + interface CredHelperConfiguration { + Optional getHelperName(); + + Map getEnvironment(); + } + Optional getFromImage(); Optional getToImage(); @@ -65,9 +71,9 @@ static interface ExtraDirectoriesConfiguration { AuthProperty getToAuth(); - Optional getFromCredHelper(); + CredHelperConfiguration getFromCredHelper(); - Optional getToCredHelper(); + CredHelperConfiguration getToCredHelper(); List getPlatforms(); diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java new file mode 100644 index 0000000000..8a8856a21a --- /dev/null +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/api/HttpRequestTester.java @@ -0,0 +1,70 @@ +/* + * Copyright 2018 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.api; + +import com.google.cloud.tools.jib.blob.Blobs; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import javax.annotation.Nullable; +import org.junit.Assert; + +/** Test helpers for making HTTP requests. */ +public class HttpRequestTester { + + /** + * Verifies the response body. Repeatedly tries {@code url} at the interval of .5 seconds for up + * to 20 seconds until getting OK HTTP response code. + */ + public static void verifyBody(String expectedBody, URL url) throws InterruptedException { + Assert.assertEquals(expectedBody, getContent(url)); + } + + /** Fetches the host to use for the http request. */ + public static String fetchDockerHostForHttpRequest() { + if (System.getenv("KOKORO_JOB_CLUSTER") != null + && System.getenv("KOKORO_JOB_CLUSTER").equals("MACOS_EXTERNAL")) { + return System.getenv("DOCKER_IP"); + } else if (System.getenv("KOKORO_JOB_CLUSTER") != null + && System.getenv("KOKORO_JOB_CLUSTER").equals("GCP_UBUNTU_DOCKER")) { + return System.getenv("DOCKER_IP_UBUNTU"); + } else { + return "localhost"; + } + } + + @Nullable + private static String getContent(URL url) throws InterruptedException { + for (int i = 0; i < 40; i++) { + Thread.sleep(500); + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { + try (InputStream in = connection.getInputStream()) { + return Blobs.writeToString(Blobs.from(in)); + } + } + } catch (IOException ignored) { + // ignored + } + } + return null; + } + + private HttpRequestTester() {} +} diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidatorTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidatorTest.java index 3243772e45..737ae4a401 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidatorTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ConfigurationPropertyValidatorTest.java @@ -16,19 +16,24 @@ package com.google.cloud.tools.jib.plugins.common; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.api.InvalidImageReferenceException; import com.google.cloud.tools.jib.api.LogEvent; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import java.util.Optional; import java.util.function.Consumer; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; /** Tests for {@link ConfigurationPropertyValidator}. */ @@ -41,60 +46,56 @@ public class ConfigurationPropertyValidatorTest { @Test public void testGetImageAuth() { - Mockito.when(mockAuth.getUsernameDescriptor()).thenReturn("user"); - Mockito.when(mockAuth.getPasswordDescriptor()).thenReturn("pass"); - Mockito.when(mockAuth.getUsername()).thenReturn("vwxyz"); - Mockito.when(mockAuth.getPassword()).thenReturn("98765"); + when(mockAuth.getUsernameDescriptor()).thenReturn("user"); + when(mockAuth.getPasswordDescriptor()).thenReturn("pass"); + when(mockAuth.getUsername()).thenReturn("vwxyz"); + when(mockAuth.getPassword()).thenReturn("98765"); // System properties set - Mockito.when(mockConfiguration.getProperty("jib.test.auth.user")) - .thenReturn(Optional.of("abcde")); - Mockito.when(mockConfiguration.getProperty("jib.test.auth.pass")) - .thenReturn(Optional.of("12345")); + when(mockConfiguration.getProperty("jib.test.auth.user")).thenReturn(Optional.of("abcde")); + when(mockConfiguration.getProperty("jib.test.auth.pass")).thenReturn(Optional.of("12345")); Credential expected = Credential.from("abcde", "12345"); Optional actual = ConfigurationPropertyValidator.getImageCredential( mockLogger, "jib.test.auth.user", "jib.test.auth.pass", mockAuth, mockConfiguration); - Assert.assertTrue(actual.isPresent()); - Assert.assertEquals(expected.toString(), actual.get().toString()); + assertThat(actual).hasValue(expected); // Auth set in configuration - Mockito.when(mockConfiguration.getProperty("jib.test.auth.user")).thenReturn(Optional.empty()); - Mockito.when(mockConfiguration.getProperty("jib.test.auth.pass")).thenReturn(Optional.empty()); + when(mockConfiguration.getProperty("jib.test.auth.user")).thenReturn(Optional.empty()); + when(mockConfiguration.getProperty("jib.test.auth.pass")).thenReturn(Optional.empty()); expected = Credential.from("vwxyz", "98765"); actual = ConfigurationPropertyValidator.getImageCredential( mockLogger, "jib.test.auth.user", "jib.test.auth.pass", mockAuth, mockConfiguration); - Assert.assertTrue(actual.isPresent()); - Assert.assertEquals(expected.toString(), actual.get().toString()); - Mockito.verify(mockLogger, Mockito.never()).accept(LogEvent.warn(Mockito.any())); + assertThat(actual).hasValue(expected); + verify(mockLogger, never()).accept(LogEvent.warn(any())); // Auth completely missing - Mockito.when(mockAuth.getUsername()).thenReturn(null); - Mockito.when(mockAuth.getPassword()).thenReturn(null); + when(mockAuth.getUsername()).thenReturn(null); + when(mockAuth.getPassword()).thenReturn(null); actual = ConfigurationPropertyValidator.getImageCredential( mockLogger, "jib.test.auth.user", "jib.test.auth.pass", mockAuth, mockConfiguration); - Assert.assertFalse(actual.isPresent()); + assertThat(actual).isEmpty(); // Password missing - Mockito.when(mockAuth.getUsername()).thenReturn("vwxyz"); - Mockito.when(mockAuth.getPassword()).thenReturn(null); + when(mockAuth.getUsername()).thenReturn("vwxyz"); + when(mockAuth.getPassword()).thenReturn(null); actual = ConfigurationPropertyValidator.getImageCredential( mockLogger, "jib.test.auth.user", "jib.test.auth.pass", mockAuth, mockConfiguration); - Assert.assertFalse(actual.isPresent()); - Mockito.verify(mockLogger) + assertThat(actual).isEmpty(); + verify(mockLogger) .accept(LogEvent.warn("pass is missing from build configuration; ignoring auth section.")); // Username missing - Mockito.when(mockAuth.getUsername()).thenReturn(null); - Mockito.when(mockAuth.getPassword()).thenReturn("98765"); + when(mockAuth.getUsername()).thenReturn(null); + when(mockAuth.getPassword()).thenReturn("98765"); actual = ConfigurationPropertyValidator.getImageCredential( mockLogger, "jib.test.auth.user", "jib.test.auth.pass", mockAuth, mockConfiguration); - Assert.assertFalse(actual.isPresent()); - Mockito.verify(mockLogger) + assertThat(actual).isEmpty(); + verify(mockLogger) .accept(LogEvent.warn("user is missing from build configuration; ignoring auth section.")); } @@ -104,24 +105,24 @@ public void testGetGeneratedTargetDockerTag() throws InvalidImageReferenceExcept new HelpfulSuggestions("", "", "to", "--to", "build.txt"); // Target configured - ProjectProperties mockProjectProperties = Mockito.mock(ProjectProperties.class); - Mockito.when(mockProjectProperties.getName()).thenReturn("project-name"); - Mockito.when(mockProjectProperties.getVersion()).thenReturn("project-version"); + ProjectProperties mockProjectProperties = mock(ProjectProperties.class); + when(mockProjectProperties.getName()).thenReturn("project-name"); + when(mockProjectProperties.getVersion()).thenReturn("project-version"); ImageReference result = ConfigurationPropertyValidator.getGeneratedTargetDockerTag( "a/b:c", mockProjectProperties, helpfulSuggestions); - Assert.assertEquals("a/b", result.getRepository()); - Assert.assertEquals("c", result.getTag().orElse(null)); - Mockito.verify(mockLogger, Mockito.never()).accept(LogEvent.lifecycle(Mockito.any())); + assertThat(result.getRepository()).isEqualTo("a/b"); + assertThat(result.getTag()).hasValue("c"); + verify(mockLogger, never()).accept(LogEvent.lifecycle(any())); // Target not configured result = ConfigurationPropertyValidator.getGeneratedTargetDockerTag( null, mockProjectProperties, helpfulSuggestions); - Assert.assertEquals("project-name", result.getRepository()); - Assert.assertEquals("project-version", result.getTag().orElse(null)); - Mockito.verify(mockProjectProperties) + assertThat(result.getRepository()).isEqualTo("project-name"); + assertThat(result.getTag()).hasValue("project-version"); + verify(mockProjectProperties) .log( LogEvent.lifecycle( "Tagging image with generated image reference project-name:project-version. If you'd " @@ -129,40 +130,43 @@ public void testGetGeneratedTargetDockerTag() throws InvalidImageReferenceExcept + "build.txt, or use the --to= commandline flag.")); // Generated tag invalid - Mockito.when(mockProjectProperties.getName()).thenReturn("%#&///*@("); - Mockito.when(mockProjectProperties.getVersion()).thenReturn("%$#//&*@($"); - try { - ConfigurationPropertyValidator.getGeneratedTargetDockerTag( - null, mockProjectProperties, helpfulSuggestions); - Assert.fail(); - } catch (InvalidImageReferenceException ignored) { - // pass - } + when(mockProjectProperties.getName()).thenReturn("%#&///*@("); + when(mockProjectProperties.getVersion()).thenReturn("%$#//&*@($"); + assertThrows( + InvalidImageReferenceException.class, + () -> + ConfigurationPropertyValidator.getGeneratedTargetDockerTag( + null, mockProjectProperties, helpfulSuggestions)); } @Test public void testParseListProperty() { - Assert.assertEquals( - ImmutableList.of("abc"), ConfigurationPropertyValidator.parseListProperty("abc")); - Assert.assertEquals( - ImmutableList.of("abcd", "efg\\,hi\\\\", "", "\\jkl\\,", "\\\\\\,mnop", ""), - ConfigurationPropertyValidator.parseListProperty( - "abcd,efg\\,hi\\\\,,\\jkl\\,,\\\\\\,mnop,")); - Assert.assertEquals(ImmutableList.of(""), ConfigurationPropertyValidator.parseListProperty("")); + assertThat(ConfigurationPropertyValidator.parseListProperty("abc")).containsExactly("abc"); + assertThat( + ConfigurationPropertyValidator.parseListProperty( + "abcd,efg\\,hi\\\\,,\\jkl\\,,\\\\\\,mnop,")) + .containsExactly("abcd", "efg,hi\\,", "\\jkl,", "\\\\,mnop", "") + .inOrder(); + assertThat(ConfigurationPropertyValidator.parseListProperty("")).containsExactly(""); + + assertThat( + ConfigurationPropertyValidator.parseListProperty( + "-Xmx2g,-agentlib:jdwp=transport=dt_socket\\,server=y\\,address=*:5005")) + .containsExactly("-Xmx2g", "-agentlib:jdwp=transport=dt_socket,server=y,address=*:5005") + .inOrder(); } @Test public void testParseMapProperty() { - Assert.assertEquals( - ImmutableMap.of("abc", "def"), ConfigurationPropertyValidator.parseMapProperty("abc=def")); - Assert.assertEquals( - ImmutableMap.of("abc", "def", "gh\\,i", "j\\\\\\,kl", "mno", "", "pqr", "stu"), - ConfigurationPropertyValidator.parseMapProperty("abc=def,gh\\,i=j\\\\\\,kl,mno=,pqr=stu")); - try { - ConfigurationPropertyValidator.parseMapProperty("not valid"); - Assert.fail(); - } catch (IllegalArgumentException ignored) { - // pass - } + assertThat(ConfigurationPropertyValidator.parseMapProperty("abc=def")) + .containsExactly("abc", "def"); + assertThat( + ConfigurationPropertyValidator.parseMapProperty( + "abc=def,gh\\,i=j\\\\\\,kl,mno=,pqr=stu")) + .containsExactly("abc", "def", "gh,i", "j\\\\,kl", "mno", "", "pqr", "stu") + .inOrder(); + assertThrows( + IllegalArgumentException.class, + () -> ConfigurationPropertyValidator.parseMapProperty("not valid")); } } diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrieversTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrieversTest.java index 4d80419827..16a765fc56 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrieversTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/DefaultCredentialRetrieversTest.java @@ -16,6 +16,13 @@ package com.google.cloud.tools.jib.plugins.common; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.google.cloud.tools.jib.api.Credential; import com.google.cloud.tools.jib.api.CredentialRetriever; import com.google.cloud.tools.jib.frontend.CredentialRetrieverFactory; @@ -25,21 +32,16 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; -import org.hamcrest.CoreMatchers; -import org.hamcrest.MatcherAssert; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; /** Tests for {@link DefaultCredentialRetrievers}. */ @@ -53,6 +55,9 @@ public class DefaultCredentialRetrieversTest { @Mock private CredentialRetriever mockKnownCredentialRetriever; @Mock private CredentialRetriever mockInferredCredentialRetriever; @Mock private CredentialRetriever mockWellKnownCredentialHelpersCredentialRetriever; + @Mock private CredentialRetriever mockXdgPrimaryCredentialRetriever; + @Mock private CredentialRetriever mockEnvHomeXdgCredentialRetriever; + @Mock private CredentialRetriever mockSystemHomeXdgCredentialRetriever; @Mock private CredentialRetriever mockDockerConfigEnvDockerConfigCredentialRetriever; @Mock private CredentialRetriever mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever; @Mock private CredentialRetriever mockDockerConfigEnvLegacyDockerConfigCredentialRetriever; @@ -80,62 +85,68 @@ public void setUp() { "HOME", Paths.get("/env/home").toString(), "DOCKER_CONFIG", - Paths.get("/docker_config").toString()); + Paths.get("/docker_config").toString(), + "XDG_RUNTIME_DIR", + Paths.get("/run/user/1000").toString(), + "XDG_CONFIG_HOME", + Paths.get("/env/home/.config").toString()); - Mockito.when(mockCredentialRetrieverFactory.dockerCredentialHelper(Mockito.anyString())) + when(mockCredentialRetrieverFactory.dockerCredentialHelper(anyString())) .thenReturn(mockDockerCredentialHelperCredentialRetriever); - Mockito.when(mockCredentialRetrieverFactory.known(knownCredential, "credentialSource")) + when(mockCredentialRetrieverFactory.known(knownCredential, "credentialSource")) .thenReturn(mockKnownCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.known(inferredCredential, "inferredCredentialSource")) + when(mockCredentialRetrieverFactory.known(inferredCredential, "inferredCredentialSource")) .thenReturn(mockInferredCredentialRetriever); - Mockito.when(mockCredentialRetrieverFactory.wellKnownCredentialHelpers()) + when(mockCredentialRetrieverFactory.wellKnownCredentialHelpers()) .thenReturn(mockWellKnownCredentialHelpersCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.dockerConfig(Paths.get("/docker_config/config.json"))) + + when(mockCredentialRetrieverFactory.dockerConfig( + Paths.get("/run/user/1000/containers/auth.json"))) + .thenReturn(mockXdgPrimaryCredentialRetriever); + when(mockCredentialRetrieverFactory.dockerConfig( + Paths.get("/env/home/.config/containers/auth.json"))) + .thenReturn(mockEnvHomeXdgCredentialRetriever); + + when(mockCredentialRetrieverFactory.dockerConfig( + Paths.get("/system/home/.config/containers/auth.json"))) + .thenReturn(mockSystemHomeXdgCredentialRetriever); + + when(mockCredentialRetrieverFactory.dockerConfig(Paths.get("/docker_config/config.json"))) .thenReturn(mockDockerConfigEnvDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.dockerConfig( - Paths.get("/docker_config/.dockerconfigjson"))) + when(mockCredentialRetrieverFactory.dockerConfig(Paths.get("/docker_config/.dockerconfigjson"))) .thenReturn(mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.legacyDockerConfig( - Paths.get("/docker_config/.dockercfg"))) + when(mockCredentialRetrieverFactory.legacyDockerConfig(Paths.get("/docker_config/.dockercfg"))) .thenReturn(mockDockerConfigEnvLegacyDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.dockerConfig( - Paths.get("/system/home/.docker/config.json"))) + when(mockCredentialRetrieverFactory.dockerConfig(Paths.get("/system/home/.docker/config.json"))) .thenReturn(mockSystemHomeDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.dockerConfig( - Paths.get("/system/home/.docker/.dockerconfigjson"))) + when(mockCredentialRetrieverFactory.dockerConfig( + Paths.get("/system/home/.docker/.dockerconfigjson"))) .thenReturn(mockSystemHomeKubernetesDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.legacyDockerConfig( - Paths.get("/system/home/.docker/.dockercfg"))) + when(mockCredentialRetrieverFactory.legacyDockerConfig( + Paths.get("/system/home/.docker/.dockercfg"))) .thenReturn(mockSystemHomeLegacyDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.dockerConfig(Paths.get("/env/home/.docker/config.json"))) + when(mockCredentialRetrieverFactory.dockerConfig(Paths.get("/env/home/.docker/config.json"))) .thenReturn(mockEnvHomeDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.dockerConfig( - Paths.get("/env/home/.docker/.dockerconfigjson"))) + when(mockCredentialRetrieverFactory.dockerConfig( + Paths.get("/env/home/.docker/.dockerconfigjson"))) .thenReturn(mockEnvHomeKubernetesDockerConfigCredentialRetriever); - Mockito.when( - mockCredentialRetrieverFactory.legacyDockerConfig( - Paths.get("/env/home/.docker/.dockercfg"))) + when(mockCredentialRetrieverFactory.legacyDockerConfig( + Paths.get("/env/home/.docker/.dockercfg"))) .thenReturn(mockEnvHomeLegacyDockerConfigCredentialRetriever); - Mockito.when(mockCredentialRetrieverFactory.googleApplicationDefaultCredentials()) + when(mockCredentialRetrieverFactory.googleApplicationDefaultCredentials()) .thenReturn(mockApplicationDefaultCredentialRetriever); } @Test public void testAsList() throws FileNotFoundException { - List credentialRetrievers = + List retriever = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retriever) + .containsExactly( + mockXdgPrimaryCredentialRetriever, + mockEnvHomeXdgCredentialRetriever, + mockSystemHomeXdgCredentialRetriever, mockDockerConfigEnvDockerConfigCredentialRetriever, mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever, mockDockerConfigEnvLegacyDockerConfigCredentialRetriever, @@ -146,23 +157,26 @@ public void testAsList() throws FileNotFoundException { mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - credentialRetrievers); + mockApplicationDefaultCredentialRetriever) + .inOrder(); } @Test public void testAsList_all() throws FileNotFoundException { - List credentialRetrievers = + List retrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .setKnownCredential(knownCredential, "credentialSource") .setInferredCredential(inferredCredential, "inferredCredentialSource") .setCredentialHelper("credentialHelperSuffix") .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retrievers) + .containsExactly( mockKnownCredentialRetriever, mockDockerCredentialHelperCredentialRetriever, mockInferredCredentialRetriever, + mockXdgPrimaryCredentialRetriever, + mockEnvHomeXdgCredentialRetriever, + mockSystemHomeXdgCredentialRetriever, mockDockerConfigEnvDockerConfigCredentialRetriever, mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever, mockDockerConfigEnvLegacyDockerConfigCredentialRetriever, @@ -173,27 +187,29 @@ public void testAsList_all() throws FileNotFoundException { mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - credentialRetrievers); + mockApplicationDefaultCredentialRetriever) + .inOrder(); - Mockito.verify(mockCredentialRetrieverFactory).known(knownCredential, "credentialSource"); - Mockito.verify(mockCredentialRetrieverFactory) - .known(inferredCredential, "inferredCredentialSource"); - Mockito.verify(mockCredentialRetrieverFactory) + verify(mockCredentialRetrieverFactory).known(knownCredential, "credentialSource"); + verify(mockCredentialRetrieverFactory).known(inferredCredential, "inferredCredentialSource"); + verify(mockCredentialRetrieverFactory) .dockerCredentialHelper("docker-credential-credentialHelperSuffix"); } @Test public void testAsList_credentialHelperPath() throws IOException { Path fakeCredentialHelperPath = temporaryFolder.newFile("fake-credHelper").toPath(); - DefaultCredentialRetrievers defaultCredentialRetrievers = + DefaultCredentialRetrievers credentialRetrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .setCredentialHelper(fakeCredentialHelperPath.toString()); - List credentialRetrievers = defaultCredentialRetrievers.asList(); - Assert.assertEquals( - Arrays.asList( + List retrievers = credentialRetrievers.asList(); + assertThat(retrievers) + .containsExactly( mockDockerCredentialHelperCredentialRetriever, + mockXdgPrimaryCredentialRetriever, + mockEnvHomeXdgCredentialRetriever, + mockSystemHomeXdgCredentialRetriever, mockDockerConfigEnvDockerConfigCredentialRetriever, mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever, mockDockerConfigEnvLegacyDockerConfigCredentialRetriever, @@ -204,43 +220,41 @@ public void testAsList_credentialHelperPath() throws IOException { mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - credentialRetrievers); - Mockito.verify(mockCredentialRetrieverFactory) + mockApplicationDefaultCredentialRetriever) + .inOrder(); + verify(mockCredentialRetrieverFactory) .dockerCredentialHelper(fakeCredentialHelperPath.toString()); Files.delete(fakeCredentialHelperPath); - try { - defaultCredentialRetrievers.asList(); - Assert.fail("Expected FileNotFoundException"); - } catch (FileNotFoundException ex) { - Assert.assertEquals( - "Specified credential helper was not found: " + fakeCredentialHelperPath, - ex.getMessage()); - } + Exception ex = assertThrows(FileNotFoundException.class, credentialRetrievers::asList); + assertThat(ex) + .hasMessageThat() + .isEqualTo("Specified credential helper was not found: " + fakeCredentialHelperPath); } @Test public void testDockerConfigRetrievers_undefinedHome() throws FileNotFoundException { - List credentialRetrievers = + List retrievers = new DefaultCredentialRetrievers( mockCredentialRetrieverFactory, new Properties(), new HashMap<>()) .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retrievers) + .containsExactly( mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - credentialRetrievers); + mockApplicationDefaultCredentialRetriever) + .inOrder(); } @Test public void testDockerConfigRetrievers_noDuplicateRetrievers() throws FileNotFoundException { properties.setProperty("user.home", Paths.get("/env/home").toString()); - List credentialRetrievers = + List retrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retrievers) + .containsExactly( + mockXdgPrimaryCredentialRetriever, + mockEnvHomeXdgCredentialRetriever, mockDockerConfigEnvDockerConfigCredentialRetriever, mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever, mockDockerConfigEnvLegacyDockerConfigCredentialRetriever, @@ -248,8 +262,8 @@ public void testDockerConfigRetrievers_noDuplicateRetrievers() throws FileNotFou mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - credentialRetrievers); + mockApplicationDefaultCredentialRetriever) + .inOrder(); environment = ImmutableMap.of( @@ -257,46 +271,45 @@ public void testDockerConfigRetrievers_noDuplicateRetrievers() throws FileNotFou Paths.get("/env/home").toString(), "DOCKER_CONFIG", Paths.get("/env/home/.docker").toString()); - credentialRetrievers = + retrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retrievers) + .containsExactly( + mockEnvHomeXdgCredentialRetriever, mockEnvHomeDockerConfigCredentialRetriever, mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - credentialRetrievers); + mockApplicationDefaultCredentialRetriever) + .inOrder(); } @Test public void testCredentialHelper_cmdExtension() throws IOException { Path credHelper = temporaryFolder.newFile("foo.cmd").toPath(); Path pathWithoutCmd = credHelper.getParent().resolve("foo"); - Assert.assertEquals(pathWithoutCmd.getParent().resolve("foo.cmd"), credHelper); + assertThat(credHelper).isEqualTo(pathWithoutCmd.getParent().resolve("foo.cmd")); DefaultCredentialRetrievers credentialRetrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .setCredentialHelper(pathWithoutCmd.toString()); - try { - credentialRetrievers.asList(); - Assert.fail("shouldn't check .cmd suffix on non-Windows"); - } catch (FileNotFoundException ex) { - MatcherAssert.assertThat( - ex.getMessage(), CoreMatchers.startsWith("Specified credential helper was not found:")); - MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.endsWith("foo")); - } + Exception ex = assertThrows(FileNotFoundException.class, credentialRetrievers::asList); + assertThat(ex).hasMessageThat().startsWith("Specified credential helper was not found:"); + assertThat(ex).hasMessageThat().endsWith("foo"); properties.setProperty("os.name", "winDOWs"); - List retrieverList = + List retrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .setCredentialHelper(pathWithoutCmd.toString()) .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retrievers) + .containsExactly( mockDockerCredentialHelperCredentialRetriever, + mockXdgPrimaryCredentialRetriever, + mockEnvHomeXdgCredentialRetriever, + mockSystemHomeXdgCredentialRetriever, mockDockerConfigEnvDockerConfigCredentialRetriever, mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever, mockDockerConfigEnvLegacyDockerConfigCredentialRetriever, @@ -307,37 +320,35 @@ public void testCredentialHelper_cmdExtension() throws IOException { mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - retrieverList); + mockApplicationDefaultCredentialRetriever) + .inOrder(); } @Test public void testCredentialHelper_exeExtension() throws IOException { Path credHelper = temporaryFolder.newFile("foo.exe").toPath(); Path pathWithoutExe = credHelper.getParent().resolve("foo"); - Assert.assertEquals(pathWithoutExe.getParent().resolve("foo.exe"), credHelper); + assertThat(credHelper).isEqualTo(pathWithoutExe.getParent().resolve("foo.exe")); DefaultCredentialRetrievers credentialRetrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .setCredentialHelper(pathWithoutExe.toString()); - try { - credentialRetrievers.asList(); - Assert.fail("shouldn't check .exe suffix on non-Windows"); - } catch (FileNotFoundException ex) { - MatcherAssert.assertThat( - ex.getMessage(), CoreMatchers.startsWith("Specified credential helper was not found:")); - MatcherAssert.assertThat(ex.getMessage(), CoreMatchers.endsWith("foo")); - } + Exception ex = assertThrows(FileNotFoundException.class, credentialRetrievers::asList); + assertThat(ex).hasMessageThat().startsWith("Specified credential helper was not found:"); + assertThat(ex).hasMessageThat().endsWith("foo"); properties.setProperty("os.name", "winDOWs"); - List retrieverList = + List retrievers = new DefaultCredentialRetrievers(mockCredentialRetrieverFactory, properties, environment) .setCredentialHelper(pathWithoutExe.toString()) .asList(); - Assert.assertEquals( - Arrays.asList( + assertThat(retrievers) + .containsExactly( mockDockerCredentialHelperCredentialRetriever, + mockXdgPrimaryCredentialRetriever, + mockEnvHomeXdgCredentialRetriever, + mockSystemHomeXdgCredentialRetriever, mockDockerConfigEnvDockerConfigCredentialRetriever, mockDockerConfigEnvKubernetesDockerConfigCredentialRetriever, mockDockerConfigEnvLegacyDockerConfigCredentialRetriever, @@ -348,7 +359,7 @@ public void testCredentialHelper_exeExtension() throws IOException { mockEnvHomeKubernetesDockerConfigCredentialRetriever, mockEnvHomeLegacyDockerConfigCredentialRetriever, mockWellKnownCredentialHelpersCredentialRetriever, - mockApplicationDefaultCredentialRetriever), - retrieverList); + mockApplicationDefaultCredentialRetriever) + .inOrder(); } } diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java index 156c4dd638..037a5b31b1 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java @@ -30,7 +30,8 @@ public class ImageMetadataOutputTest { + "\"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\"," + "\"imageDigest\":" + "\"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc\"," - + "\"tags\":[\"latest\",\"tag\"]" + + "\"tags\":[\"latest\",\"tag\"]," + + "\"imagePushed\":true" + "}"; @Test @@ -43,6 +44,7 @@ public void testFromJson() throws IOException { Assert.assertEquals( "sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc", output.getImageDigest()); + Assert.assertTrue(output.isImagePushed()); Assert.assertEquals(ImmutableList.of("latest", "tag"), output.getTags()); } diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java index b724f2babe..4b41447b55 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java @@ -242,6 +242,7 @@ public void testBuildImage_writesImageJson() throws Exception { Mockito.when(mockJibContainer.getTags()).thenReturn(tags); Mockito.when(mockJibContainerBuilder.containerize(mockContainerizer)) .thenReturn(mockJibContainer); + Mockito.when(mockJibContainer.isImagePushed()).thenReturn(true); testJibBuildRunner.writeImageJson(outputPath).runBuild(); final String outputJson = new String(Files.readAllBytes(outputPath), StandardCharsets.UTF_8); @@ -250,5 +251,6 @@ public void testBuildImage_writesImageJson() throws Exception { Assert.assertEquals(imageId, metadataOutput.getImageId()); Assert.assertEquals(digest, metadataOutput.getImageDigest()); Assert.assertEquals(tags, ImmutableSet.copyOf(metadataOutput.getTags())); + Assert.assertTrue(metadataOutput.isImagePushed()); } } diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/MainClassResolverTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/MainClassResolverTest.java index d94cc41fe1..c5134b099a 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/MainClassResolverTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/MainClassResolverTest.java @@ -92,8 +92,7 @@ public void testResolveMainClass_multipleInferredWithInvalidMainClassFromJarPlug .thenReturn( new DirectoryWalker( Paths.get(Resources.getResource("core/class-finder-tests/multiple").toURI())) - .walk() - .asList()); + .walk()); try { MainClassResolver.resolveMainClass(null, mockProjectProperties); @@ -126,8 +125,7 @@ public void testResolveMainClass_multipleInferredWithoutMainClassFromJarPlugin() .thenReturn( new DirectoryWalker( Paths.get(Resources.getResource("core/class-finder-tests/multiple").toURI())) - .walk() - .asList()); + .walk()); try { MainClassResolver.resolveMainClass(null, mockProjectProperties); Assert.fail(); diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java index 8547a6ce7b..51404d1351 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessorTest.java @@ -43,6 +43,7 @@ import com.google.cloud.tools.jib.api.buildplan.ModificationTimeProvider; import com.google.cloud.tools.jib.api.buildplan.Platform; import com.google.cloud.tools.jib.configuration.ImageConfiguration; +import com.google.cloud.tools.jib.plugins.common.RawConfiguration.CredHelperConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.ExtraDirectoriesConfiguration; import com.google.cloud.tools.jib.plugins.common.RawConfiguration.PlatformConfiguration; import com.google.common.collect.ImmutableList; @@ -66,6 +67,8 @@ import java.util.Optional; import java.util.function.Consumer; import javax.annotation.Nullable; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -76,10 +79,11 @@ import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** Tests for {@link PluginConfigurationProcessor}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnitParamsRunner.class) public class PluginConfigurationProcessorTest { private static class TestPlatformConfiguration implements PlatformConfiguration { @@ -147,6 +151,7 @@ private static List getLayerEntries(ContainerBuildPlan buildPlan, Str .getEntries(); } + @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule().silent(); @Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties(); @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -158,6 +163,8 @@ private static List getLayerEntries(ContainerBuildPlan buildPlan, Str @Mock private InferredAuthProvider inferredAuthProvider; @Mock private AuthProperty authProperty; @Mock private Consumer logger; + @Mock private CredHelperConfiguration fromCredHelper; + @Mock private CredHelperConfiguration toCredHelper; private Path appCacheDirectory; private final JibContainerBuilder jibContainerBuilder = Jib.fromScratch(); @@ -172,6 +179,8 @@ public void setUp() throws IOException, InvalidImageReferenceException, Inferred when(rawConfiguration.getFilesModificationTime()).thenReturn("EPOCH_PLUS_SECOND"); when(rawConfiguration.getCreationTime()).thenReturn("EPOCH"); when(rawConfiguration.getContainerizingMode()).thenReturn("exploded"); + when(rawConfiguration.getFromCredHelper()).thenReturn(fromCredHelper); + when(rawConfiguration.getToCredHelper()).thenReturn(toCredHelper); when(projectProperties.getMajorJavaVersion()).thenReturn(8); when(projectProperties.getToolName()).thenReturn("tool"); when(projectProperties.getToolVersion()).thenReturn("tool-version"); @@ -195,7 +204,8 @@ public void testPluginConfigurationProcessor_defaults() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { ContainerBuildPlan buildPlan = processCommonConfiguration(); assertThat(buildPlan.getEntrypoint()) @@ -216,7 +226,8 @@ public void testPluginConfigurationProcessor_extraDirectory() InvalidAppRootException, IOException, IncompatibleBaseImageJavaVersionException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidImageReferenceException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { Path extraDirectory = Paths.get(Resources.getResource("core/layer").toURI()); Mockito.>when(rawConfiguration.getExtraDirectories()) .thenReturn(Arrays.asList(new TestExtraDirectoriesConfiguration(extraDirectory))); @@ -255,13 +266,26 @@ public void testPluginConfigurationProcessor_extraDirectory() assertThat(fooEntry.get().getPermissions().toOctalString()).isEqualTo("123"); } + @Test + public void testPluginConfigurationProcessor__errorOnExtraDirectoryPathNotFound() + throws URISyntaxException, NumberFormatException { + Path extraDirectory = Paths.get(Resources.getResource("core/layer").toURI()).resolve("xyz"); + Mockito.>when(rawConfiguration.getExtraDirectories()) + .thenReturn(Arrays.asList(new TestExtraDirectoriesConfiguration(extraDirectory))); + + Exception exception = + assertThrows(ExtraDirectoryNotFoundException.class, this::processCommonConfiguration); + assertThat(exception).hasMessageThat().isEqualTo(extraDirectory.toString()); + } + @Test public void testPluginConfigurationProcessor_cacheDirectorySystemProperties() throws InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidImageReferenceException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { System.setProperty(PropertyNames.BASE_IMAGE_CACHE, "new/base/cache"); System.setProperty(PropertyNames.APPLICATION_CACHE, "/new/application/cache"); @@ -336,7 +360,8 @@ public void testEntrypoint() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); @@ -442,13 +467,14 @@ public void testEntrypoint_defaultWarPackaging() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(projectProperties.isWarProject()).thenReturn(true); ContainerBuildPlan buildPlan = processCommonConfiguration(); assertThat(buildPlan.getEntrypoint()) - .containsExactly("java", "-jar", "/usr/local/jetty/start.jar") + .containsExactly("java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy") .inOrder(); verifyNoInteractions(logger); } @@ -459,7 +485,8 @@ public void testEntrypoint_defaultNonWarPackaging() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(projectProperties.isWarProject()).thenReturn(false); ContainerBuildPlan buildPlan = processCommonConfiguration(); @@ -479,7 +506,8 @@ public void testEntrypoint_extraClasspathNonWarPackaging() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getExtraClasspath()).thenReturn(Collections.singletonList("/foo")); when(projectProperties.isWarProject()).thenReturn(false); @@ -500,7 +528,8 @@ public void testClasspathArgumentFile() InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getExtraClasspath()).thenReturn(Collections.singletonList("/foo")); when(projectProperties.getMajorJavaVersion()).thenReturn(9); @@ -539,7 +568,8 @@ public void testClasspathArgumentFile_mainClassInferenceFailureWithCustomEntrypo InvalidAppRootException, IOException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getMainClass()).thenReturn(Optional.of("invalid main class")); when(rawConfiguration.getEntrypoint()).thenReturn(Optional.of(Arrays.asList("bash"))); @@ -575,7 +605,8 @@ public void testUser() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getUser()).thenReturn(Optional.of("customUser")); ContainerBuildPlan buildPlan = processCommonConfiguration(); @@ -588,7 +619,8 @@ public void testUser_null() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { ContainerBuildPlan buildPlan = processCommonConfiguration(); assertThat(buildPlan.getUser()).isNull(); } @@ -599,7 +631,8 @@ public void testEntrypoint_warningOnJvmFlags() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); when(rawConfiguration.getJvmFlags()).thenReturn(Collections.singletonList("jvmFlag")); @@ -609,7 +642,7 @@ public void testEntrypoint_warningOnJvmFlags() assertThat(buildPlan.getEntrypoint()).containsExactly("custom", "entrypoint").inOrder(); verify(projectProperties) .log( - LogEvent.warn( + LogEvent.info( "mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored " + "when entrypoint is specified")); } @@ -620,7 +653,8 @@ public void testEntrypoint_warningOnMainclass() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); when(rawConfiguration.getMainClass()).thenReturn(Optional.of("java.util.Object")); @@ -630,7 +664,7 @@ public void testEntrypoint_warningOnMainclass() assertThat(buildPlan.getEntrypoint()).containsExactly("custom", "entrypoint").inOrder(); verify(projectProperties) .log( - LogEvent.warn( + LogEvent.info( "mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored " + "when entrypoint is specified")); } @@ -641,7 +675,8 @@ public void testEntrypoint_warningOnExpandClasspathDependencies() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getEntrypoint()) .thenReturn(Optional.of(Arrays.asList("custom", "entrypoint"))); when(rawConfiguration.getExpandClasspathDependencies()).thenReturn(true); @@ -651,7 +686,7 @@ public void testEntrypoint_warningOnExpandClasspathDependencies() assertThat(buildPlan.getEntrypoint()).containsExactly("custom", "entrypoint").inOrder(); verify(projectProperties) .log( - LogEvent.warn( + LogEvent.info( "mainClass, extraClasspath, jvmFlags, and expandClasspathDependencies are ignored " + "when entrypoint is specified")); } @@ -662,14 +697,14 @@ public void testEntrypoint_warningOnMainclassForWar() IncompatibleBaseImageJavaVersionException, InvalidPlatformException, InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidFilesModificationTimeException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, ExtraDirectoryNotFoundException { when(rawConfiguration.getMainClass()).thenReturn(Optional.of("java.util.Object")); when(projectProperties.isWarProject()).thenReturn(true); ContainerBuildPlan buildPlan = processCommonConfiguration(); assertThat(buildPlan.getEntrypoint()) - .containsExactly("java", "-jar", "/usr/local/jetty/start.jar") + .containsExactly("java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy") .inOrder(); verify(projectProperties) .log( @@ -684,14 +719,14 @@ public void testEntrypoint_warningOnExpandClasspathDependenciesForWar() IncompatibleBaseImageJavaVersionException, InvalidPlatformException, InvalidContainerVolumeException, MainClassInferenceException, InvalidAppRootException, InvalidWorkingDirectoryException, InvalidFilesModificationTimeException, - InvalidContainerizingModeException { + InvalidContainerizingModeException, ExtraDirectoryNotFoundException { when(rawConfiguration.getExpandClasspathDependencies()).thenReturn(true); when(projectProperties.isWarProject()).thenReturn(true); ContainerBuildPlan buildPlan = processCommonConfiguration(); assertThat(buildPlan.getEntrypoint()) - .containsExactly("java", "-jar", "/usr/local/jetty/start.jar") + .containsExactly("java", "-jar", "/usr/local/jetty/start.jar", "--module=ee10-deploy") .inOrder(); verify(projectProperties) .log( @@ -706,7 +741,8 @@ public void testEntrypointClasspath_nonDefaultAppRoot() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(rawConfiguration.getAppRoot()).thenReturn("/my/app"); ContainerBuildPlan buildPlan = processCommonConfiguration(); @@ -723,7 +759,8 @@ public void testWebAppEntrypoint_inheritedFromCustomBaseImage() InvalidAppRootException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { when(projectProperties.isWarProject()).thenReturn(true); when(rawConfiguration.getFromImage()).thenReturn(Optional.of("custom-base-image")); @@ -846,7 +883,7 @@ public void testGetDefaultBaseImage_nonWarPackaging() when(projectProperties.isWarProject()).thenReturn(false); assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:8-jre"); + .isEqualTo("eclipse-temurin:8-jre"); } @Test @@ -859,48 +896,34 @@ public void testGetDefaultBaseImage_warProject() } @Test - public void testGetDefaultBaseImage_chooseJava8BaseImage() - throws IncompatibleBaseImageJavaVersionException { - when(projectProperties.getMajorJavaVersion()).thenReturn(6); + @Parameters( + value = { + "6, eclipse-temurin:8-jre", + "8, eclipse-temurin:8-jre", + "9, eclipse-temurin:11-jre", + "11, eclipse-temurin:11-jre", + "13, eclipse-temurin:17-jre", + "17, eclipse-temurin:17-jre", + "21, eclipse-temurin:21-jre" + }) + public void testGetDefaultBaseImage_defaultJavaBaseImage( + int javaVersion, String expectedBaseImage) throws IncompatibleBaseImageJavaVersionException { + when(projectProperties.getMajorJavaVersion()).thenReturn(javaVersion); assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:8-jre"); - - when(projectProperties.getMajorJavaVersion()).thenReturn(7); - assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:8-jre"); - - when(projectProperties.getMajorJavaVersion()).thenReturn(8); - assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:8-jre"); + .isEqualTo(expectedBaseImage); } @Test - public void testGetDefaultBaseImage_chooseJava11BaseImage() - throws IncompatibleBaseImageJavaVersionException { - when(projectProperties.getMajorJavaVersion()).thenReturn(9); - assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:11-jre"); - - when(projectProperties.getMajorJavaVersion()).thenReturn(10); - assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:11-jre"); - - when(projectProperties.getMajorJavaVersion()).thenReturn(11); - assertThat(PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)) - .isEqualTo("adoptopenjdk:11-jre"); - } - - @Test - public void testGetDefaultBaseImage_projectHigherThanJava11() { - when(projectProperties.getMajorJavaVersion()).thenReturn(12); + public void testGetDefaultBaseImage_projectHigherThanJava21() { + when(projectProperties.getMajorJavaVersion()).thenReturn(22); IncompatibleBaseImageJavaVersionException exception = assertThrows( IncompatibleBaseImageJavaVersionException.class, () -> PluginConfigurationProcessor.getDefaultBaseImage(projectProperties)); - assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(11); - assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(12); + assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(21); + assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(22); } @Test @@ -947,53 +970,34 @@ public void testGetJavaContainerBuilderWithBaseImage_registryWithPrefix() } @Test - public void testGetJavaContainerBuilderWithBaseImage_incompatibleJava8BaseImage() { - when(projectProperties.getMajorJavaVersion()).thenReturn(11); - - when(rawConfiguration.getFromImage()).thenReturn(Optional.of("adoptopenjdk:8")); - IncompatibleBaseImageJavaVersionException exception1 = - assertThrows( - IncompatibleBaseImageJavaVersionException.class, - () -> - PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage( - rawConfiguration, projectProperties, inferredAuthProvider)); - assertThat(exception1.getBaseImageMajorJavaVersion()).isEqualTo(8); - assertThat(exception1.getProjectMajorJavaVersion()).isEqualTo(11); - - when(rawConfiguration.getFromImage()).thenReturn(Optional.of("adoptopenjdk:8-jre")); - IncompatibleBaseImageJavaVersionException exception2 = - assertThrows( - IncompatibleBaseImageJavaVersionException.class, - () -> - PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage( - rawConfiguration, projectProperties, inferredAuthProvider)); - assertThat(exception2.getBaseImageMajorJavaVersion()).isEqualTo(8); - assertThat(exception2.getProjectMajorJavaVersion()).isEqualTo(11); - } - - @Test - public void testGetJavaContainerBuilderWithBaseImage_incompatibleJava11BaseImage() { - when(projectProperties.getMajorJavaVersion()).thenReturn(15); - - when(rawConfiguration.getFromImage()).thenReturn(Optional.of("adoptopenjdk:11")); - IncompatibleBaseImageJavaVersionException exception1 = - assertThrows( - IncompatibleBaseImageJavaVersionException.class, - () -> - PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage( - rawConfiguration, projectProperties, inferredAuthProvider)); - assertThat(exception1.getBaseImageMajorJavaVersion()).isEqualTo(11); - assertThat(exception1.getProjectMajorJavaVersion()).isEqualTo(15); - - when(rawConfiguration.getFromImage()).thenReturn(Optional.of("adoptopenjdk:11-jre")); - IncompatibleBaseImageJavaVersionException exception2 = + @Parameters( + value = { + "adoptopenjdk:8, 8, 11", + "adoptopenjdk:8-jre, 8, 11", + "eclipse-temurin:8, 8, 11", + "eclipse-temurin:8-jre, 8, 11", + "adoptopenjdk:11, 11, 15", + "adoptopenjdk:11-jre, 11, 15", + "eclipse-temurin:11, 11, 15", + "eclipse-temurin:11-jre, 11, 15", + "eclipse-temurin:17, 17, 19", + "eclipse-temurin:17-jre, 17, 19", + "eclipse-temurin:21, 21, 22", + "eclipse-temurin:21-jre, 21, 22" + }) + public void testGetJavaContainerBuilderWithBaseImage_incompatibleJavaBaseImage( + String baseImage, int baseImageJavaVersion, int appJavaVersion) { + when(projectProperties.getMajorJavaVersion()).thenReturn(appJavaVersion); + + when(rawConfiguration.getFromImage()).thenReturn(Optional.of(baseImage)); + IncompatibleBaseImageJavaVersionException exception = assertThrows( IncompatibleBaseImageJavaVersionException.class, () -> PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage( rawConfiguration, projectProperties, inferredAuthProvider)); - assertThat(exception2.getBaseImageMajorJavaVersion()).isEqualTo(11); - assertThat(exception2.getProjectMajorJavaVersion()).isEqualTo(15); + assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(baseImageJavaVersion); + assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(appJavaVersion); } // https://github.com/GoogleContainerTools/jib/issues/1995 @@ -1009,8 +1013,8 @@ public void testGetJavaContainerBuilderWithBaseImage_java12BaseImage() } @Test - public void testGetJavaContainerBuilderWithBaseImage_java12NoBaseImage() { - when(projectProperties.getMajorJavaVersion()).thenReturn(12); + public void testGetJavaContainerBuilderWithBaseImage_java22NoBaseImage() { + when(projectProperties.getMajorJavaVersion()).thenReturn(22); when(rawConfiguration.getFromImage()).thenReturn(Optional.empty()); IncompatibleBaseImageJavaVersionException exception = assertThrows( @@ -1018,8 +1022,8 @@ public void testGetJavaContainerBuilderWithBaseImage_java12NoBaseImage() { () -> PluginConfigurationProcessor.getJavaContainerBuilderWithBaseImage( rawConfiguration, projectProperties, inferredAuthProvider)); - assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(11); - assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(12); + assertThat(exception.getBaseImageMajorJavaVersion()).isEqualTo(21); + assertThat(exception.getProjectMajorJavaVersion()).isEqualTo(22); } @Test @@ -1199,7 +1203,8 @@ private ContainerBuildPlan processCommonConfiguration() IOException, InvalidWorkingDirectoryException, InvalidPlatformException, InvalidContainerVolumeException, IncompatibleBaseImageJavaVersionException, NumberFormatException, InvalidContainerizingModeException, - InvalidFilesModificationTimeException, InvalidCreationTimeException { + InvalidFilesModificationTimeException, InvalidCreationTimeException, + ExtraDirectoryNotFoundException { JibContainerBuilder containerBuilder = PluginConfigurationProcessor.processCommonConfiguration( rawConfiguration, ignored -> Optional.empty(), projectProperties, containerizer); diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/UpdateCheckerTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/UpdateCheckerTest.java index 4c04f7fdbe..7b918ee2e0 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/UpdateCheckerTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/UpdateCheckerTest.java @@ -68,8 +68,9 @@ public void tearDown() throws IOException { } @Test - public void testPerformUpdateCheck_newVersionFound() throws IOException { + public void testPerformUpdateCheck_newVersionFound() throws IOException, InterruptedException { Instant before = Instant.now(); + Thread.sleep(100); setupLastUpdateCheck(); Optional message = UpdateChecker.performUpdateCheck( @@ -99,8 +100,9 @@ public void testPerformUpdateCheck_newJsonField() } @Test - public void testPerformUpdateCheck_onLatest() throws IOException { + public void testPerformUpdateCheck_onLatest() throws IOException, InterruptedException { Instant before = Instant.now(); + Thread.sleep(100); setupLastUpdateCheck(); Optional message = UpdateChecker.performUpdateCheck( @@ -115,8 +117,9 @@ public void testPerformUpdateCheck_onLatest() throws IOException { } @Test - public void testPerformUpdateCheck_noLastUpdateCheck() throws IOException { + public void testPerformUpdateCheck_noLastUpdateCheck() throws IOException, InterruptedException { Instant before = Instant.now(); + Thread.sleep(100); Optional message = UpdateChecker.performUpdateCheck( configDir, "1.0.2", testWebServer.getEndpoint(), "tool-name", ignored -> {}); @@ -129,9 +132,11 @@ public void testPerformUpdateCheck_noLastUpdateCheck() throws IOException { } @Test - public void testPerformUpdateCheck_emptyLastUpdateCheck() throws IOException { + public void testPerformUpdateCheck_emptyLastUpdateCheck() + throws IOException, InterruptedException { Files.createFile(configDir.resolve("lastUpdateCheck")); Instant before = Instant.now(); + Thread.sleep(100); Optional message = UpdateChecker.performUpdateCheck( configDir, "1.0.2", testWebServer.getEndpoint(), "tool-name", ignored -> {}); @@ -164,8 +169,9 @@ public void testPerformUpdateCheck_lastUpdateCheckTooSoon() throws IOException { } @Test - public void testPerformUpdateCheck_badLastUpdateTime() throws IOException { + public void testPerformUpdateCheck_badLastUpdateTime() throws IOException, InterruptedException { Instant before = Instant.now(); + Thread.sleep(100); Files.write( configDir.resolve("lastUpdateCheck"), "bad timestamp".getBytes(StandardCharsets.UTF_8)); Optional message = diff --git a/kokoro/continuous.bat b/kokoro/continuous.bat index f7e2264453..54aeb8e391 100755 --- a/kokoro/continuous.bat +++ b/kokoro/continuous.bat @@ -1,8 +1,7 @@ @echo on REM Java 9 does not work with Mockito mockmaker. -set JAVA_HOME=c:\program files\java\jdk1.8.0_152 -set PATH=%JAVA_HOME%\bin;%PATH% +echo %JAVA_HOME% cd github/jib diff --git a/kokoro/continuous.sh b/kokoro/continuous.sh index fabb102a64..0e8bf52ed8 100755 --- a/kokoro/continuous.sh +++ b/kokoro/continuous.sh @@ -5,28 +5,34 @@ set -o xtrace gcloud components install docker-credential-gcr +# Docker service does not run by default in Big Sur but can be started with the following commands. +if [ "${KOKORO_JOB_CLUSTER}" = "MACOS_EXTERNAL" ]; then + source github/jib/kokoro/docker_setup_macos.sh +fi + +# In GCP_UBUNTU_DOCKER, the build script runs in a container and requires additional setup +if [ "${KOKORO_JOB_CLUSTER}" = "GCP_UBUNTU_DOCKER" ]; then + source github/jib/kokoro/docker_setup_ubuntu.sh +fi + # docker-credential-gcr uses GOOGLE_APPLICATION_CREDENTIALS as the credentials key file export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_KEYSTORE_DIR}/72743_jib_integration_testing_key + +# Overrides default search order to check credentials in GOOGLE_APPLICATION_CREDENTIALS +docker-credential-gcr config --token-source="env" docker-credential-gcr configure-docker +# From default hostname, get id of container to exclude +CONTAINER_ID=$(hostname) +echo "$CONTAINER_ID" + # Stops any left-over containers. -docker stop $(docker ps --all --quiet) || true -docker kill $(docker ps --all --quiet) || true +docker stop $(docker ps --all --quiet | grep -v "$CONTAINER_ID") || true +docker kill $(docker ps --all --quiet | grep -v "$CONTAINER_ID") || true # Sets the integration testing project. export JIB_INTEGRATION_TESTING_PROJECT=jib-integration-testing -# Restarting Docker for Mac to get around the certificate expiration issue: -# b/112707824 -# https://github.com/GoogleContainerTools/jib/issues/730#issuecomment-413603874 -# https://github.com/moby/moby/issues/11534 -# TODO: remove this temporary fix once b/112707824 is permanently fixed. -if [ "${KOKORO_JOB_CLUSTER}" = "MACOS_EXTERNAL" ]; then - osascript -e 'quit app "Docker"' - open -a Docker - while ! docker info > /dev/null 2>&1; do sleep 1; done -fi - cd github/jib -./gradlew clean build integrationTest --info --stacktrace +./gradlew clean build integrationTest --info --stacktrace \ No newline at end of file diff --git a/kokoro/docker_setup_macos.sh b/kokoro/docker_setup_macos.sh new file mode 100644 index 0000000000..66422462e3 --- /dev/null +++ b/kokoro/docker_setup_macos.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +docker-machine ls +docker-machine start default +export DOCKER_IP="$(docker-machine ip default)" +echo $DOCKER_IP + +docker-machine ssh default "echo '{ \"insecure-registries\":[\"$DOCKER_IP:5000\", \"$DOCKER_IP:6000\", \"$DOCKER_IP:8080\"] }' | sudo tee /etc/docker/daemon.json " +docker-machine ssh default "echo 'DOCKER_OPTS=\"--config-file=/etc/docker/daemon.json\"' | sudo tee -a /var/lib/boot2docker/profile " +docker-machine ssh default "mkdir /home/docker/auth; docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > /home/docker/auth/htpasswd" +docker-machine ssh default "sudo /etc/init.d/docker restart" +docker-machine inspect default +docker-machine env default +eval "$(docker-machine env default)" + +export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk-8-latest/Contents/Home" +export PATH=$JAVA_HOME/bin:$PATH +echo $JAVA_HOME \ No newline at end of file diff --git a/kokoro/docker_setup_ubuntu.sh b/kokoro/docker_setup_ubuntu.sh new file mode 100644 index 0000000000..b6bfd60f72 --- /dev/null +++ b/kokoro/docker_setup_ubuntu.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +export DOCKER_IP_UBUNTU="$(/sbin/ip route|awk '/default/ { print $3 }')" +echo "DOCKER_IP_UBUNTU: ${DOCKER_IP_UBUNTU}" +echo "${DOCKER_IP_UBUNTU} localhost" >> /etc/hosts +mkdir -p /tmpfs/auth +docker run --entrypoint htpasswd httpd:2 -Bbn testuser testpassword > /tmpfs/auth/htpasswd \ No newline at end of file diff --git a/kokoro/presubmit.bat b/kokoro/presubmit.bat index 4b2280097c..224bd15c8c 100755 --- a/kokoro/presubmit.bat +++ b/kokoro/presubmit.bat @@ -1,8 +1,7 @@ @echo on REM Java 9 does not work with Mockito mockmaker. -set JAVA_HOME=c:\program files\java\jdk1.8.0_152 -set PATH=%JAVA_HOME%\bin;%PATH% +echo %JAVA_HOME% cd github/jib diff --git a/kokoro/presubmit.sh b/kokoro/presubmit.sh index a2c836e6ac..96d3acea40 100755 --- a/kokoro/presubmit.sh +++ b/kokoro/presubmit.sh @@ -5,21 +5,24 @@ set -o xtrace gcloud components install docker-credential-gcr -# Stops any left-over containers. -docker stop $(docker ps --all --quiet) || true -docker kill $(docker ps --all --quiet) || true - -# Restarting Docker for Mac to get around the certificate expiration issue: -# b/112707824 -# https://github.com/GoogleContainerTools/jib/issues/730#issuecomment-413603874 -# https://github.com/moby/moby/issues/11534 -# TODO: remove this temporary fix once b/112707824 is permanently fixed. +# Docker service does not run by default in Big Sur but can be started with the following commands. if [ "${KOKORO_JOB_CLUSTER}" = "MACOS_EXTERNAL" ]; then - osascript -e 'quit app "Docker"' - open -a Docker - while ! docker info > /dev/null 2>&1; do sleep 1; done + source github/jib/kokoro/docker_setup_macos.sh +fi + +# In GCP_UBUNTU_DOCKER, the build script runs in a container and requires additional setup +if [ "${KOKORO_JOB_CLUSTER}" = "GCP_UBUNTU_DOCKER" ]; then + source github/jib/kokoro/docker_setup_ubuntu.sh fi +# From default hostname, get id of container to exclude +CONTAINER_ID=$(hostname) +echo "$CONTAINER_ID" + +# Stops any left-over containers. +docker stop $(docker ps --all --quiet | grep -v "$CONTAINER_ID") || true +docker kill $(docker ps --all --quiet | grep -v "$CONTAINER_ID") || true + cd github/jib # we only run integration tests on jib-core for presubmit diff --git a/proposals/buildfile.md b/proposals/buildfile.md index de4eaa4559..9ea15eebce 100644 --- a/proposals/buildfile.md +++ b/proposals/buildfile.md @@ -9,7 +9,7 @@ kind: Buildfile # "FROM" with detail for manifest lists or multiple architectures from: - image: "adoptopenjdk:11-jre" + image: "eclipse-temurin:11-jre" # optional: if missing, then defaults to `linux/amd64` platforms: - architecture: "arm" @@ -18,7 +18,7 @@ from: os: darwin # potentially simple form of "FROM" (based on ability to define schema) -from: "adoptopenjdk:11-jre" +from: "eclipse-temurin:11-jre" creationTime: 0 # millis since epoch or iso8601 creation time format: Docker # Docker or OCI diff --git a/proposals/container-build-plan-spec.md b/proposals/container-build-plan-spec.md index 400a807513..4de2934749 100644 --- a/proposals/container-build-plan-spec.md +++ b/proposals/container-build-plan-spec.md @@ -2,13 +2,13 @@ Specification for building a container image. -Although looking similar, the structure and semantics of similary named properties are different from the Docker/[OCI Image Configuration](https://github.com/opencontainers/image-spec/blob/master/config.md). +Although 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). ## Example ``` { - "baseImage": "adoptopenjdk:11-jre@sha256:318dc2103fb30d0e02c373d04f7c337229268879af9fb3f77f89c6a9037efcb1", + "baseImage": "eclipse-temurin:11-jre@sha256:a3036d6a01859e3fe8cbad4887ccb2e4afd5f5ce8f085ca6bc0302fcbb8601f7", "architectureHint": "amd64", "osHint": "linux", "format": "Docker", diff --git a/proposals/jib-cli-surface.md b/proposals/jib-cli-surface.md index 3a2a36c946..b291db365e 100644 --- a/proposals/jib-cli-surface.md +++ b/proposals/jib-cli-surface.md @@ -21,7 +21,7 @@ build build a container registry://gcr.io/project/image docker://some-image-ref tar://relative/path.tar - tar:///aboslute/path.tar + tar:///absolute/path.tar ``` ### Optional @@ -49,7 +49,7 @@ build build a container Credentials can be specified using credential helpers or username + password. The following options are available ``` --credential-helper credential helper to use for registries, a path or name suffix (docker-credential-) - --to-crendential-helper credential helper to use only for the target registry + --to-credential-helper credential helper to use only for the target registry --from-credential-helper credential helper to use only for the base image registry --username configure a username for authenticating against registries diff --git a/settings.gradle b/settings.gradle index ea596bd104..96d62451bc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,18 @@ +pluginManagement { + repositories { + mavenCentral() + // Workaround from: https://github.com/gradle/gradle/issues/15406#issuecomment-1020352934 + gradlePluginPortal { + this as MavenArtifactRepository + metadataSources { + mavenPom() + artifact() + ignoreGradleMetadataRedirection() + } + } + } +} + include ":jib-build-plan" include ":jib-plugins-extension-common" include ":jib-gradle-plugin-extension-api"