diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 8d0640c7..cb0e74a0 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1 @@
-* @jonesbusy
+* @andreaTP
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 49aa9112..4d809c33 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -1,4 +1,4 @@
-# Copyright The ORAS Authors.
+# Copyright The ORAS Authors and roastedroot contributors.
# 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
@@ -11,7 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-name: 🐛 Bug report
+name: Bug report
description: File a bug report
labels: ["bug"]
body:
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index badc3481..1969d1ba 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -1,4 +1,4 @@
-# Copyright The ORAS Authors.
+# Copyright The ORAS Authors and roastedroot contributors.
# 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
@@ -18,7 +18,7 @@ body:
- type: markdown
id: preface
attributes:
- value: "Thank you for submitting new features for ORAS."
+ value: "Thank you for submitting new features for the ORAS Java SDK."
- type: textarea
id: description
attributes:
@@ -28,7 +28,7 @@ body:
- type: textarea
id: solution
attributes:
- label: "Why is this needed for ORAS?"
+ label: "Why is this needed?"
description: "Please describe your user story or scenario."
validations:
required: true
@@ -36,7 +36,6 @@ body:
id: idea
attributes:
label: "Are you willing to submit PRs to contribute to this feature?"
- description: "This is absolutely not required, but we are happy to guide you in the contribution process
- especially when you already have a good proposal or understanding of how to implement it. Join us at https://slack.cncf.io/ and choose #oras channel."
+ description: "This is absolutely not required, but we are happy to guide you in the contribution process."
options:
- label: Yes, I am willing to implement it.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index ed00a7f4..d36f38fe 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -28,5 +28,5 @@
-->
### Submitter checklist
-- [ ] I have read and understood the [CONTRIBUTING](https://github.com/oras-project/oras-java/blob/main/CONTRIBUTING.md) guide
+- [ ] I have read and understood the [CONTRIBUTING](https://github.com/roastedroot/oras-java/blob/main/CONTRIBUTING.md) guide
- [ ] I have run `mvn license:update-file-header`, `mvn spotless:apply`, `pre-commit run -a`, `mvn clean install` before opening the PR
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
deleted file mode 100644
index a7a9afc3..00000000
--- a/.github/release-drafter.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-# Configuration for Release Drafter: https://github.com/toolmantim/release-drafter
----
-
-# Always use 3 digits for the version number
-name-template: $NEXT_PATCH_VERSION
-tag-template: $NEXT_PATCH_VERSION
-version-template: $MAJOR.$MINOR.$PATCH
-
-categories:
- - title: 💥 Breaking changes
- labels:
- - breaking
- - title: ⚠️ Deprecated
- labels:
- - deprecated
- - title: 🚀 New features and improvements
- labels:
- - enhancement
- - feature
- - title: 🐛 Bug fixes
- labels:
- - bug
- - fix
- - bugfix
- - regression
- - regression-fix
- - title: 📝 Documentation updates
- labels:
- - documentation
- - title: 👻 Maintenance
- labels:
- - chore
- - internal
- - maintenance
- - title: 🚦 Tests
- labels:
- - test
- - tests
- - title: ✍ Other changes
- # Default label used by Dependabot
- - title: 📦 Dependency updates
- labels:
- - dependencies
- collapse-after: 15
-exclude-labels:
- - reverted
- - no-changelog
- - skip-changelog
- - invalid
-
-template: |
-
- $CHANGES
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ee780dcb..03b8e5fd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -3,10 +3,7 @@ name: PR build
on:
workflow_dispatch:
pull_request:
- branches: [main, release/0.2.x]
- push:
- branches:
- - release/0.2.x
+ branches: [main]
permissions:
contents: read
@@ -35,7 +32,7 @@ jobs:
needs: build
strategy:
matrix:
- java: [17, 21, 25]
+ java: [11, 17, 21, 25]
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -59,42 +56,3 @@ jobs:
- name: Maven build
run: mvn --batch-mode --update-snapshots verify
-
- - name: Upload coverage reports to Codecov
- if: matrix.java == '17'
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
- with:
- report_type: coverage
- token: ${{ secrets.CODECOV_TOKEN }}
- verbose: true
-
- - name: Upload test reports to Codecov
- if: matrix.java == '17'
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
- with:
- report_type: test_results
- token: ${{ secrets.CODECOV_TOKEN }}
- verbose: true
-
- maven-build-windows:
- runs-on: windows-latest
- needs: build
- steps:
- - name: Checkout
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
-
- - name: Read Maven version
- id: maven-version
- run: echo "version=$(cat .github/vars/maven-version.txt)" >> $env:GITHUB_OUTPUT
-
- - name: Setup Maven Action
- uses: s4u/setup-maven-action@ba34de01b7f4ba2ab8e2860df8993a29f4477056 # v1.20.0
- with:
- checkout-enabled: false
- java-distribution: 'temurin'
- java-version: 17
- maven-version: ${{ steps.maven-version.outputs.version }}
- cache-enabled: true
-
- - name: Maven build
- run: mvn --batch-mode --update-snapshots verify
diff --git a/.github/workflows/deploy-javadoc.yml b/.github/workflows/deploy-javadoc.yml
index 9a62bcf6..587fd5c6 100644
--- a/.github/workflows/deploy-javadoc.yml
+++ b/.github/workflows/deploy-javadoc.yml
@@ -30,7 +30,7 @@ jobs:
with:
checkout-enabled: false
java-distribution: 'temurin'
- java-version: 17
+ java-version: 11
maven-version: ${{ steps.maven-version.outputs.version }}
cache-enabled: true
diff --git a/.github/workflows/deploy-snapshots.yml b/.github/workflows/deploy-snapshots.yml
deleted file mode 100644
index f2d48441..00000000
--- a/.github/workflows/deploy-snapshots.yml
+++ /dev/null
@@ -1,68 +0,0 @@
-name: Deploy Snaphosts
-
-on:
- workflow_dispatch:
- push:
- branches:
- - main
-
-permissions:
- contents: read
-
-jobs:
- deploy-snapshots:
- runs-on: ubuntu-latest
- permissions:
- contents: read
- id-token: write
- packages: write
- steps:
-
- - name: Checkout
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
-
- - name: Read Maven version
- id: maven-version
- run: echo "version=$(cat .github/vars/maven-version.txt)" >> $GITHUB_OUTPUT
-
- - name: Setup Maven Action
- uses: s4u/setup-maven-action@ba34de01b7f4ba2ab8e2860df8993a29f4477056 # v1.20.0
- with:
- checkout-enabled: false
- java-distribution: 'temurin'
- java-version: 17
- maven-version: ${{ steps.maven-version.outputs.version }}
- cache-enabled: true
- settings-servers: |
- [{
- "id": "central-snapshots",
- "username": "${{ secrets.CENTRAL_USERNAME }}",
- "password": "${{ secrets.CENTRAL_PASSWORD }}"
- }]
-
- - name: Import GPG key
- uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
- with:
- gpg_private_key: ${{ secrets.GPG_KEY }}
- passphrase: ${{ secrets.GPG_PASSPHRASE }}
- - name: List keys
- run: gpg -K
-
- - name: Maven build
- run: mvn --batch-mode -Psign --update-snapshots -Dmaven.resolver.transport=wagon deploy
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
- with:
- report_type: coverage
- token: ${{ secrets.CODECOV_TOKEN }}
-
- - name: Upload test reports to Codecov
- if: matrix.java == '17'
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
- with:
- report_type: test_results
- token: ${{ secrets.CODECOV_TOKEN }}
- verbose: true
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
deleted file mode 100644
index 4ff54ca0..00000000
--- a/.github/workflows/release-drafter.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Release Drafter
-
-on:
- workflow_dispatch:
- push:
- branches:
- - main
-
-permissions:
- contents: read
-
-jobs:
- update_release_draft:
- permissions:
- contents: write
- pull-requests: write
- runs-on: ubuntu-latest
- steps:
- - uses: release-drafter/release-drafter@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7.7.0
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f714648c..49e75276 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,86 +1,80 @@
-name: Release
+name: release
on:
workflow_dispatch:
+ inputs:
+ release-version:
+ description: 'Version being released'
+ required: true
+ branch:
+ description: 'Branch to release from'
+ required: true
+ default: 'main'
permissions:
- contents: read
+ contents: write
jobs:
- deploy-release:
+ release:
+ name: Release
runs-on: ubuntu-latest
- permissions:
- contents: write
- id-token: write
- packages: write
- steps:
+ steps:
- name: Checkout
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
-
- - name: Read Maven version
- id: maven-version
- run: echo "version=$(cat .github/vars/maven-version.txt)" >> $GITHUB_OUTPUT
+ uses: actions/checkout@v7
- - name: Setup Maven Action
- uses: s4u/setup-maven-action@ba34de01b7f4ba2ab8e2860df8993a29f4477056 # v1.20.0
+ - name: Setup Java
+ uses: actions/setup-java@v5
with:
- checkout-enabled: false
- java-distribution: 'temurin'
- java-version: 17
- maven-version: ${{ steps.maven-version.outputs.version }}
- cache-enabled: true
- settings-servers: |
- [{
- "id": "central",
- "username": "${{ secrets.CENTRAL_USERNAME }}",
- "password": "${{ secrets.CENTRAL_PASSWORD }}"
- }]
+ java-version: 11
+ distribution: 'temurin'
+ server-id: central
+ server-username: MAVEN_USERNAME
+ server-password: MAVEN_CENTRAL_TOKEN
+ gpg-private-key: ${{ secrets.java_gpg_secret_key }}
+ gpg-passphrase: MAVEN_GPG_PASSPHRASE
- - name: Configure Git
+ - id: install-secret-key
+ name: Install gpg secret key
run: |
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git config gpg.format openpgp
- git config commit.gpgsign true
- git config user.signingkey CB5831CAFA9D8C6AFA9FC9DA67E69853B952BA35
+ cat <(echo -e "${{ secrets.java_gpg_secret_key }}") | gpg --batch --import
+ gpg --list-secret-keys --keyid-format LONG
- - name: Import GPG key
- uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
- with:
- gpg_private_key: ${{ secrets.GPG_KEY }}
- passphrase: ${{ secrets.GPG_PASSPHRASE }}
- - name: List keys
- run: gpg -K
+ - name: Install docker-credential-fake
+ run: sudo cp docker-credential-fake /usr/bin/docker-credential-fake
- - name: Setup SSH
- uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
- with:
- ssh-private-key: ${{ secrets.RELEASE_SSH_KEY }}
+ - name: Compile
+ run: mvn --batch-mode clean install -DskipTests
- - name: Maven release
- run: mvn --batch-mode --update-snapshots -Dmaven.resolver.transport=wagon -DskipTests -Darguments="-DskipTests" release:prepare release:perform
+ - name: Setup Git
+ run: |
+ git config --global user.name "GitHub Actions"
+ git config --global user.email "actions@github.com"
+
+ - name: Set the version
+ run: |
+ mvn versions:set -DgenerateBackupPoms=false -DnewVersion=${{ github.event.inputs.release-version }}
+ git add .
+ git commit -m "Release version update ${{ github.event.inputs.release-version }}"
+ git push
+ git tag ${{ github.event.inputs.release-version }}
+ git push origin ${{ github.event.inputs.release-version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Finalize release
+ - name: Release to Maven Central
+ run: |
+ mvn --batch-mode deploy -Prelease -DskipTests=true
+ env:
+ MAVEN_USERNAME: ${{ secrets.central_username }}
+ MAVEN_CENTRAL_TOKEN: ${{ secrets.central_password }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.java_gpg_passphrase }}
+
+ - name: Back to Snapshot
run: |
- draft_id=$(gh api /repos/oras-project/oras-java/releases | jq -r '.[] | select(.draft == true) | .id')
- echo "Draft ID: $draft_id"
- gh api -X PATCH -F draft=false /repos/oras-project/oras-java/releases/$draft_id
- release_tag_name=$(gh api /repos/oras-project/oras-java/releases/latest | jq -r '.tag_name')
- echo "Release tag: $release_tag_name"
- for file in target/checkout/target/*.jar target/checkout/target/*.jar.asc target/checkout/target/*.jar.sigstore.json; do
- if [[ -f "$file" ]]; then
- if [[ "$file" != *javadoc* && "$file" != *sources* && "$file" != *tests* ]]; then
- echo "Uploading $file"
- gh release upload "$release_tag_name" "$file" --repo oras-project/oras-java
- else
- echo "Skipping $file"
- fi
- else
- echo "Skipping missing file: $file"
- fi
- done
+ mvn versions:set -DgenerateBackupPoms=false -DnewVersion=999-SNAPSHOT
+ git add .
+ git commit -m "Snapshot version update"
+ git push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
deleted file mode 100644
index 80c81d61..00000000
--- a/.github/workflows/scorecard.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-name: Security ossf scorecard
-on:
- workflow_dispatch:
- push:
- branches: [ "main" ]
-
-permissions: read-all
-
-jobs:
- analysis:
- name: Scorecard analysis
- runs-on: ubuntu-latest
- permissions:
- security-events: write
- id-token: write
-
- steps:
- - name: Checkout code
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- with:
- persist-credentials: false
-
- - name: Run analysis
- uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
- with:
- results_file: results.sarif
- results_format: sarif
- publish_results: true
-
- - name: Upload artifact
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with:
- name: SARIF file
- path: results.sarif
- retention-days: 5
-
- - name: Upload to code-scanning
- uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v3.29.5
- with:
- sarif_file: results.sarif
diff --git a/.github/workflows/updatecli.yaml b/.github/workflows/updatecli.yaml
deleted file mode 100644
index c1d2dbfe..00000000
--- a/.github/workflows/updatecli.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: updatecli
-on:
- workflow_dispatch:
- schedule:
- - cron: '0 2 * * 1' # Every Monday at 2am UTC
- push:
- pull_request:
-permissions:
- contents: read
-
-jobs:
- updatecli:
- permissions:
- contents: write
- pull-requests: write
- runs-on: ubuntu-latest
- if: github.repository_owner == 'oras-project'
- steps:
-
- - name: Checkout code
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- with:
- persist-credentials: false
-
- - name: Install Updatecli in the runner
- uses: updatecli/updatecli-action@41b9c8d707830a9daebaeaa84c1b62d60b779564 # v3.6.0
-
- - name: Run Updatecli in Dry Run mode
- run: updatecli diff --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml
- env:
- UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Run Updatecli in Apply mode
- if: github.ref == 'refs/heads/main'
- run: updatecli apply --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml
- env:
- UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.sdkmanrc b/.sdkmanrc
index ff53b5e2..7559d988 100644
--- a/.sdkmanrc
+++ b/.sdkmanrc
@@ -1,2 +1,2 @@
-java=17.0.19-tem
+java=11.0.25-tem
maven=3.9.16
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 24660b62..67e09e28 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,14 +1,12 @@
# Contributing
-Please refer to the [ORAS Contributing guide](https://oras.land/docs/community/contributing_guide) for general aspects.
+Thank you for your interest in contributing to the ORAS Java SDK (Java 11 Fork).
## Java SDK
-This section is specific to the Java SDK.
-
### Requirements
-- Java 17 or later
+- Java 11 or later
- Maven 3.9.9 or later
- Container engine like Docker or Podman (due to testcontainers)
- Pre-commit `3.6.2` or later
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..5479e408
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,8 @@
+ORAS Java SDK (Java 11 Fork)
+Copyright 2026 roastedroot contributors
+
+This product includes software developed by the ORAS project
+(https://github.com/oras-project/oras-java)
+
+Original work: Copyright 2024-2026 The ORAS Authors
+Licensed under the Apache License, Version 2.0
diff --git a/README.md b/README.md
index 5688bc7e..bacb99b1 100644
--- a/README.md
+++ b/README.md
@@ -1,67 +1,31 @@
-# ORAS Java
+# ORAS Java SDK (Java 11 Fork)
-[](https://github.com/oras-project/oras-java/actions/workflows/build.yml)
-[](https://codecov.io/gh/oras-project/oras-java)
-
-[](https://github.com/oras-project/oras-java/blob/main/LICENSE)
-[](https://oras-project.github.io/oras-java/)
-[](https://scorecard.dev/viewer/?uri=github.com/oras-project/oras-java)
-[](https://www.bestpractices.dev/projects/10047)
-[](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/land/oras/oras-java-sdk/README.md)
+[](https://github.com/roastedroot/oras-java/actions/workflows/build.yml)
-
-> [!WARNING]
-> The Oras Java SDK is currently in **alpha** state.
+> **Note:** This is a fork of [oras-project/oras-java](https://github.com/oras-project/oras-java)
+> maintained with **Java 11** compatibility. The upstream project targets Java 17+.
>
-> It's configuration and APIs might change in future releases
-
-
-
-
+> The Java packages remain `land.oras.*` — you can switch between this fork and
+> upstream by changing only the Maven/Gradle coordinates.
OCI Registry as Storage enables libraries to push OCI Artifacts to [OCI Conformant](https://github.com/opencontainers/oci-conformance) registries. This is a Java SDK for Java developers to empower them to do this in their applications.
## Consuming SDK
-SNAPSHOT for version 0.2.x are published on GitHub Maven packages.
-SNAPSHOT for version 0.3.x and above are published on Maven Central at: https://central.sonatype.com/repository/maven-snapshots/
-
-Releases are published on Maven Central since version 0.2.x.
-
-Javadoc is published from main branch into: https://oras-project.github.io/oras-java/
+### Maven
```xml
- land.oras
- oras-java-sdk
+ io.roastedroot
+ oras-java
VERSION_HERE
```
-### Quarkus
-
-Quarkus users can use the extension `quarkus-oras` to use the SDK in their applications.
-
-Follow the [Quarkus ORAS documentation](https://docs.quarkiverse.io/quarkus-oras/dev/index.html#) to get started with Quarkus.
-
-### Only for SNAPSHOTS (only for testing)
+### Gradle
-Then on your `pom.xml`
-
-```xml
-
-
- central-snapshots
- ORAS Maven Central SNAPSHOTS
- https://central.sonatype.com/repository/maven-snapshots/
-
- true
-
-
- false
-
-
-
+```groovy
+implementation 'io.roastedroot:oras-java:VERSION_HERE'
```
## Examples
@@ -310,7 +274,7 @@ security decision is evaluated against the **effective (resolved) reference**
the reference originally supplied. For each operation the SDK:
1. **Resolves** the reference: short-name / unqualified-search expansion (e.g.
- `nginx` → `docker.io/library/nginx`), `prefix` → `location` rewrites, and mirror
+ `nginx` -> `docker.io/library/nginx`), `prefix` -> `location` rewrites, and mirror
selection.
2. **Evaluates** `blocked`, `insecure` (HTTP vs HTTPS) and the trust policy against
that resolved reference.
@@ -367,10 +331,10 @@ When a policy is set, every manifest/index pull is evaluated against it and reje
| Type | Supported | Behaviour |
|--------------------------|-----------|--------------------------------------------------------------------|
-| `insecureAcceptAnything` | ✅ | Accept the image without any verification (trust all). |
-| `reject` | ✅ | Reject the image unconditionally. |
-| `sigstoreSigned` | ✅ | Accept only images with a valid keyed Sigstore (cosign) signature. |
-| `signedBy` (GPG) | ❌ | Not implemented. Legacy |
+| `insecureAcceptAnything` | Yes | Accept the image without any verification (trust all). |
+| `reject` | Yes | Reject the image unconditionally. |
+| `sigstoreSigned` | Yes | Accept only images with a valid keyed Sigstore (cosign) signature. |
+| `signedBy` (GPG) | No | Not implemented. Legacy |
### `sigstoreSigned`
@@ -401,19 +365,11 @@ cosign bundle payload carries only the image digest and no claimed Docker refere
}
```
-### Deploy SNAPSHOTS
-
-SNAPSHOTS are automatically deployed when the `main` branch is updated. See the [GitHub Actions](.github/workflows/deploy-snapshots.yml) for more details.
-
-### Perform release
-
-- Ensure the draft release version correspond to the version on the `pom.xml`. Specially if changing the major or minor version. Patch releases are automatically updated.
-- Run the release workflow
-
-## Code of Conduct
+## Attribution
-Please note that this project has adopted the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
-Please follow it in all your interactions with the project members and users.
+This project is a fork of [oras-project/oras-java](https://github.com/oras-project/oras-java),
+originally created by [Valentin Delaye](https://github.com/jonesbusy) and the ORAS Authors.
+We are grateful to the original contributors for their work on the ORAS Java SDK.
## License
diff --git a/SECURITY.md b/SECURITY.md
index ffefe341..99a19303 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,3 +1,5 @@
# Security Policy
-Please follow the [security policy](https://oras.land/docs/community/reporting_security_concerns) to report a security vulnerability or concern.
+To report a security vulnerability, please use
+[GitHub Security Advisories](https://github.com/roastedroot/oras-java/security/advisories)
+for this repository.
diff --git a/pom.xml b/pom.xml
index f858cf3f..52da3e48 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,13 +2,13 @@
4.0.0
- land.oras
- oras-java-sdk
- 0.8.3-SNAPSHOT
+ io.roastedroot
+ oras-java
+ 999-SNAPSHOT
jar
${project.groupId}:${project.artifactId}
- ORAS Java SDK
- https://github.com/oras-project/oras-java
+ ORAS Java SDK - Java 11 compatible fork
+ https://github.com/roastedroot/oras-java
@@ -24,23 +24,21 @@
ORAS
https://oras.land/
+
+ andreaTP
+ Andrea Peruffo
+ andrea.peruffo1982@gmail.com
+ IBM
+
- scm:git:git@github.com:oras-project/oras-java.git
- scm:git:git@github.com:oras-project/oras-java.git
+ scm:git:git://github.com/roastedroot/oras-java.git
+ scm:git:ssh://github.com:roastedroot/oras-java.git
HEAD
- https://github.com/oras-project/oras-java
+ https://github.com/roastedroot/oras-java
-
-
- central-snapshots
- ORAS Maven Central SNAPSHOTS
- https://central.sonatype.com/repository/maven-snapshots/
-
-
-
UTF-8
@@ -56,17 +54,17 @@
2.0.18
1.0.1
- 3.2.1
+ 2.18.3
1.28.0
1.5.7-13
1.85
- 3.2.4
- 1.17.0
+ 3.1.8
+ 1.13.12
1.6.2
- 6.1.3
- 2.0.5
+ 5.11.4
+ 1.21.4
3.13.2
5.23.0
2.1.8
@@ -88,22 +86,26 @@
3.5.1
3.12.0
3.4.0
- 3.3.1
3.2.8
0.11.0
- 2.2.0
- 1.0.4
2.7.1
-
- 17
- 17
- 17
+
+ 11
+ 11
+ 11
+
+ com.fasterxml.jackson
+ jackson-bom
+ ${jackson.version}
+ pom
+ import
+
io.micrometer
micrometer-bom
@@ -118,13 +120,6 @@
pom
import
-
- tools.jackson
- jackson-bom
- ${jackson.version}
- pom
- import
-
ch.qos.logback
logback-classic
@@ -187,12 +182,12 @@
org.testcontainers
- testcontainers
+ junit-jupiter
${testcontainer.version}
org.testcontainers
- testcontainers-junit-jupiter
+ testcontainers
${testcontainer.version}
@@ -210,6 +205,26 @@
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-toml
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
com.github.ben-manes.caffeine
caffeine
@@ -238,22 +253,6 @@
org.slf4j
slf4j-api
-
- tools.jackson.core
- jackson-core
-
-
- tools.jackson.core
- jackson-databind
-
-
- tools.jackson.dataformat
- jackson-dataformat-toml
-
-
- tools.jackson.dataformat
- jackson-dataformat-yaml
-
@@ -303,12 +302,12 @@
org.testcontainers
- testcontainers
+ junit-jupiter
test
org.testcontainers
- testcontainers-junit-jupiter
+ testcontainers
test
@@ -324,20 +323,6 @@
-
-
-
- false
-
-
- true
-
- apache-snapshots
- apache-snapshots
- https://repository.apache.org/content/repositories/snapshots/
-
-
-
@@ -382,19 +367,6 @@
-
- org.spdx
- spdx-maven-plugin
- ${spdx-maven-plugin.version}
-
-
- build-spdx
-
- createSPDX
-
-
-
-
org.apache.maven.plugins
maven-clean-plugin
@@ -532,19 +504,6 @@
-
- dev.sigstore
- sigstore-maven-plugin
- ${sigstore-maven-plugin.version}
-
-
- sign
-
- sign
-
-
-
-
org.apache.maven.plugins
maven-source-plugin
@@ -559,16 +518,6 @@
-
- org.apache.maven.plugins
- maven-release-plugin
- ${maven-release-plugin.version}
-
- @{project.version}
- [ci skip]
- release
-
-
org.apache.maven.plugins
maven-enforcer-plugin
@@ -670,14 +619,6 @@
org.codehaus.mojo
license-maven-plugin
-
- org.spdx
- spdx-maven-plugin
-
-
- com.diffplug.spotless
- spotless-maven-plugin
-
org.apache.maven.plugins
maven-enforcer-plugin
@@ -699,6 +640,20 @@
+
+ spotless
+
+ [17,)
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+
+
+
quick-build
@@ -706,7 +661,6 @@
true
true
true
- true
true
true
true
@@ -714,15 +668,11 @@
release
-
- true
- true
-
org.apache.maven.plugins
- maven-enforcer-plugin
+ maven-gpg-plugin
org.apache.maven.plugins
@@ -732,53 +682,10 @@
org.apache.maven.plugins
maven-source-plugin
-
- org.apache.maven.plugins
- maven-gpg-plugin
-
org.sonatype.central
central-publishing-maven-plugin
-
- org.apache.maven.plugins
- maven-release-plugin
-
-
- dev.sigstore
- sigstore-maven-plugin
-
-
-
-
-
- sign
-
-
-
- org.jacoco
- jacoco-maven-plugin
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
-
-
- dev.sigstore
- sigstore-maven-plugin
-
diff --git a/src/main/java/land/oras/Annotations.java b/src/main/java/land/oras/Annotations.java
index ec6c8119..b6133cc3 100644
--- a/src/main/java/land/oras/Annotations.java
+++ b/src/main/java/land/oras/Annotations.java
@@ -21,32 +21,88 @@
package land.oras;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import land.oras.utils.JsonUtils;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
- * Record for annotations
- *
- * @param manifestAnnotations Annotations for the manifest
- * @param configAnnotations Annotations for the config
- * @param filesAnnotations Annotations for the layers/files
+ * Annotations for an OCI artifact
*/
@NullMarked
@OrasModel
-public record Annotations(
- Map configAnnotations,
- Map manifestAnnotations,
- Map> filesAnnotations) {
+public final class Annotations {
+
+ private final Map configAnnotations;
+ private final Map manifestAnnotations;
+ private final Map> filesAnnotations;
+
+ /**
+ * Create a new annotations instance
+ * @param configAnnotations Annotations for the config
+ * @param manifestAnnotations Annotations for the manifest
+ * @param filesAnnotations Annotations for the layers/files
+ */
+ public Annotations(
+ Map configAnnotations,
+ Map manifestAnnotations,
+ Map> filesAnnotations) {
+ this.configAnnotations = configAnnotations;
+ this.manifestAnnotations = manifestAnnotations;
+ this.filesAnnotations = filesAnnotations;
+ }
@JsonCreator
private Annotations() {
this(new HashMap<>(), new HashMap<>(), new HashMap<>());
}
+ /**
+ * Get the config annotations
+ * @return The config annotations
+ */
+ @JsonProperty("configAnnotations")
+ public Map configAnnotations() {
+ return configAnnotations;
+ }
+
+ /**
+ * Get the manifest annotations
+ * @return The manifest annotations
+ */
+ @JsonProperty("manifestAnnotations")
+ public Map manifestAnnotations() {
+ return manifestAnnotations;
+ }
+
+ /**
+ * Get the files annotations
+ * @return The files annotations
+ */
+ @JsonProperty("filesAnnotations")
+ public Map> filesAnnotations() {
+ return filesAnnotations;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Annotations)) return false;
+ Annotations that = (Annotations) o;
+ return Objects.equals(configAnnotations, that.configAnnotations)
+ && Objects.equals(manifestAnnotations, that.manifestAnnotations)
+ && Objects.equals(filesAnnotations, that.filesAnnotations);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(configAnnotations, manifestAnnotations, filesAnnotations);
+ }
+
/**
* Create a new annotations record with only manifest annotations
* @param manifestAnnotations The manifest annotations
diff --git a/src/main/java/land/oras/ArtifactType.java b/src/main/java/land/oras/ArtifactType.java
index a650d48a..03839f1c 100644
--- a/src/main/java/land/oras/ArtifactType.java
+++ b/src/main/java/land/oras/ArtifactType.java
@@ -58,7 +58,7 @@ public static ArtifactType from(@Nullable String artifactType) {
}
// Must match https://datatracker.ietf.org/doc/html/rfc6838
if (!artifactType.matches("^[a-zA-Z0-9!#$%&'*+.^_`{|}~-]+/[a-zA-Z0-9!#$%&'*+.^_`{|}~-]+$")) {
- throw new OrasException("Invalid artifact type: %s".formatted(artifactType));
+ throw new OrasException(String.format("Invalid artifact type: %s", artifactType));
}
return new ArtifactType(artifactType);
}
diff --git a/src/main/java/land/oras/ContainerRef.java b/src/main/java/land/oras/ContainerRef.java
index 325c7044..843b43a6 100644
--- a/src/main/java/land/oras/ContainerRef.java
+++ b/src/main/java/land/oras/ContainerRef.java
@@ -133,7 +133,7 @@ public boolean isUnqualified() {
public String getFullRepository(@Nullable Registry registry) {
String namespace = getNamespace(registry);
if (namespace != null) {
- return "%s/%s".formatted(namespace, repository);
+ return String.format("%s/%s", namespace, repository);
}
return repository;
}
@@ -235,8 +235,8 @@ public ContainerRef withReferrersFallbackTag() {
if (digest == null) {
throw new OrasException("Digest is required to compute the referrers fallback tag");
}
- String fallbackTag = "%s-%s"
- .formatted(SupportedAlgorithm.fromDigest(digest).getPrefix(), SupportedAlgorithm.getDigest(digest));
+ String fallbackTag = String.format(
+ "%s-%s", SupportedAlgorithm.fromDigest(digest).getPrefix(), SupportedAlgorithm.getDigest(digest));
return new ContainerRef(registry, unqualified, namespace, repository, fallbackTag, null);
}
@@ -258,9 +258,9 @@ public SupportedAlgorithm getAlgorithm() {
private String getApiPrefix(@Nullable Registry target) {
String namespace = getNamespace(target);
if (namespace != null) {
- return "%s/v2/%s/%s".formatted(getApiRegistry(target), namespace, repository);
+ return String.format("%s/v2/%s/%s", getApiRegistry(target), namespace, repository);
}
- return "%s/v2/%s".formatted(getApiRegistry(target), repository);
+ return String.format("%s/v2/%s", getApiRegistry(target), repository);
}
/**
@@ -269,7 +269,7 @@ private String getApiPrefix(@Nullable Registry target) {
* @return The tag URL
*/
public String getRepositoriesPath(@Nullable Registry target) {
- return "%s/v2/_catalog".formatted(getApiRegistry(target));
+ return String.format("%s/v2/_catalog", getApiRegistry(target));
}
/**
@@ -286,7 +286,7 @@ public String getRepositoriesPath() {
* @return The tag URL
*/
public String getTagsPath(@Nullable Registry target) {
- return "%s/tags/list".formatted(getApiPrefix(target));
+ return String.format("%s/tags/list", getApiPrefix(target));
}
/**
@@ -350,11 +350,11 @@ public String getBlobsMountPath(@Nullable Registry registry, ContainerRef source
if (digest == null) {
throw new OrasException("You are required to include a digest");
}
- return "%s/blobs/uploads/?mount=%s&from=%s"
- .formatted(
- getApiPrefix(registry),
- digest,
- URLEncoder.encode(sourceRef.getFullRepository(registry), StandardCharsets.UTF_8));
+ return String.format(
+ "%s/blobs/uploads/?mount=%s&from=%s",
+ getApiPrefix(registry),
+ digest,
+ URLEncoder.encode(sourceRef.getFullRepository(registry), StandardCharsets.UTF_8));
}
/**
@@ -365,13 +365,11 @@ public String getBlobsMountPath(@Nullable Registry registry, ContainerRef source
*/
public String getReferrersPath(@Nullable Registry registry, @Nullable ArtifactType artifactType) {
if (artifactType == null) {
- return "%s/referrers/%s".formatted(getApiPrefix(registry), digest);
+ return String.format("%s/referrers/%s", getApiPrefix(registry), digest);
}
- return "%s/referrers/%s?artifactType=%s"
- .formatted(
- getApiPrefix(registry),
- digest,
- URLEncoder.encode(artifactType.toString(), StandardCharsets.UTF_8));
+ return String.format(
+ "%s/referrers/%s?artifactType=%s",
+ getApiPrefix(registry), digest, URLEncoder.encode(artifactType.toString(), StandardCharsets.UTF_8));
}
/**
@@ -410,7 +408,7 @@ public String getReferrersPath(@Nullable ArtifactType artifactType) {
* @return The manifests URL
*/
public String getManifestsPath(@Nullable Registry registry) {
- return "%s/manifests/%s".formatted(getApiPrefix(registry), digest == null ? tag : digest);
+ return String.format("%s/manifests/%s", getApiPrefix(registry), digest == null ? tag : digest);
}
/**
@@ -430,7 +428,7 @@ public String getBlobsUploadDigestPath(Registry registry) {
if (digest == null) {
throw new OrasException("You are required to include a digest");
}
- return "%s/blobs/uploads/?digest=%s".formatted(getApiPrefix(registry), digest);
+ return String.format("%s/blobs/uploads/?digest=%s", getApiPrefix(registry), digest);
}
/**
@@ -439,7 +437,7 @@ public String getBlobsUploadDigestPath(Registry registry) {
* @return The blobs upload URL
*/
public String getBlobsUploadPath(Registry registry) {
- return "%s/blobs/uploads/".formatted(getApiPrefix(registry));
+ return String.format("%s/blobs/uploads/", getApiPrefix(registry));
}
/**
@@ -451,7 +449,7 @@ public String getBlobsPath(@Nullable Registry registry) {
if (digest == null) {
throw new OrasException("You are required to include a digest");
}
- return "%s/blobs/%s".formatted(getApiPrefix(registry), digest);
+ return String.format("%s/blobs/%s", getApiPrefix(registry), digest);
}
/**
@@ -607,7 +605,7 @@ ContainerRef checkBlocked(Registry registry) throws OrasException {
// Check registry configuration (blocked registries)
if (isBlocked(registry)) {
throw new OrasException(
- "Access to container reference %s is blocked by registry configuration".formatted(this));
+ String.format("Access to container reference %s is blocked by registry configuration", this));
}
return this;
}
@@ -637,7 +635,7 @@ public ContainerRef forRegistry(Registry registry) {
String key = registry.getRegistriesConf().getAliasKey(this);
if (registry.getRegistry() == null && registry.getRegistriesConf().hasAlias(key)) {
String newLocation = registry.getRegistriesConf().getAliases().get(key);
- String newRefString = "%s:%s".formatted(newLocation, tag);
+ String newRefString = String.format("%s:%s", newLocation, tag);
LOG.debug("Using {} as an alias to {}", key, newRefString);
return ContainerRef.parse(newRefString);
}
@@ -678,9 +676,9 @@ private String determineFirstUnqualifiedSearchRegistry(Registry registry) {
}
LOG.debug("Container {} does not exist in unqualified search registry {}", this, searchRegistry);
}
- throw new OrasException(
- "Container reference %s is unqualified and cannot be found in any of the unqualified search registries: %s"
- .formatted(this, registry.getRegistriesConf().getUnqualifiedRegistries()));
+ throw new OrasException(String.format(
+ "Container reference %s is unqualified and cannot be found in any of the unqualified search registries: %s",
+ this, registry.getRegistriesConf().getUnqualifiedRegistries()));
}
@Override
@@ -705,14 +703,14 @@ public String toString() {
if (isUnqualified()) {
if (namespace != null && !namespace.isEmpty()) {
- return "%s/%s%s".formatted(namespace, repository, ref);
+ return String.format("%s/%s%s", namespace, repository, ref);
}
- return "%s%s".formatted(repository, ref);
+ return String.format("%s%s", repository, ref);
}
if (namespace != null && !namespace.isEmpty()) {
- return "%s/%s/%s%s".formatted(registry, namespace, repository, ref);
+ return String.format("%s/%s/%s%s", registry, namespace, repository, ref);
}
- return "%s/%s%s".formatted(registry, repository, ref);
+ return String.format("%s/%s%s", registry, repository, ref);
}
}
diff --git a/src/main/java/land/oras/CopyUtils.java b/src/main/java/land/oras/CopyUtils.java
index 557557d9..7b34d78b 100644
--- a/src/main/java/land/oras/CopyUtils.java
+++ b/src/main/java/land/oras/CopyUtils.java
@@ -25,6 +25,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@@ -224,9 +225,9 @@ void copy(
if (depth > MAX_COPY_DEPTH) {
LOG.error("Depth exceeded for copy of {}", sourceRef.getRepository());
- throw new OrasException(
- "Maximum copy recursion depth (%d) exceeded; the source may serve an unbounded index or referrer graph"
- .formatted(MAX_COPY_DEPTH));
+ throw new OrasException(String.format(
+ "Maximum copy recursion depth (%d) exceeded; the source may serve an unbounded index or referrer graph",
+ MAX_COPY_DEPTH));
}
boolean includeReferrers = options.includeReferrers();
@@ -310,7 +311,7 @@ else if (source.isIndexMediaType(contentType)) {
List filtered = index.getManifests().stream()
.filter(d ->
options.platformFilter().stream().anyMatch(p -> Platform.matches(d.getPlatform(), p)))
- .toList();
+ .collect(Collectors.toList());
if (filtered.isEmpty()) {
throw new OrasException(
"No manifests found in index matching platform filter: " + options.platformFilter());
@@ -373,7 +374,7 @@ else if (source.isIndexMediaType(contentType)) {
LOG.debug("Copied index {} with tag {}", pushedIndex, targetTag);
} else {
- throw new OrasException("Unsupported content type: %s".formatted(contentType));
+ throw new OrasException(String.format("Unsupported content type: %s", contentType));
}
}
diff --git a/src/main/java/land/oras/Descriptor.java b/src/main/java/land/oras/Descriptor.java
index 576fd5c3..4fddbb9e 100644
--- a/src/main/java/land/oras/Descriptor.java
+++ b/src/main/java/land/oras/Descriptor.java
@@ -37,7 +37,7 @@
@OrasModel
@JsonPropertyOrder({Const.JSON_PROPERTY_MEDIA_TYPE, Const.JSON_PROPERTY_DIGEST, Const.JSON_PROPERTY_SIZE})
@JsonInclude(JsonInclude.Include.NON_NULL)
-public sealed class Descriptor permits Config, Manifest, Layer, Index {
+public class Descriptor {
/**
* The media type of the layer
diff --git a/src/main/java/land/oras/Index.java b/src/main/java/land/oras/Index.java
index 2bb3c67f..804c45d6 100644
--- a/src/main/java/land/oras/Index.java
+++ b/src/main/java/land/oras/Index.java
@@ -32,6 +32,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.BiPredicate;
+import java.util.stream.Collectors;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
@@ -163,7 +164,7 @@ public List filter(Platform platform) {
public List filter(Platform platform, BiPredicate comparator) {
return getManifests().stream()
.filter(descriptor -> comparator.test(descriptor.getPlatform(), platform))
- .toList();
+ .collect(Collectors.toList());
}
/**
@@ -173,7 +174,7 @@ public List filter(Platform platform, BiPredicate unspecifiedPlatforms() {
return getManifests().stream()
.filter(descriptor -> Platform.unspecified(descriptor.getPlatform()))
- .toList();
+ .collect(Collectors.toList());
}
/**
diff --git a/src/main/java/land/oras/LayoutRef.java b/src/main/java/land/oras/LayoutRef.java
index e5389965..dc509170 100644
--- a/src/main/java/land/oras/LayoutRef.java
+++ b/src/main/java/land/oras/LayoutRef.java
@@ -198,9 +198,9 @@ public int hashCode() {
public String toString() {
if (tag != null) {
if (isValidDigest()) {
- return "%s@%s".formatted(getFolder().toString(), tag);
+ return String.format("%s@%s", getFolder().toString(), tag);
}
- return "%s:%s".formatted(getFolder().toString(), tag);
+ return String.format("%s:%s", getFolder().toString(), tag);
} else {
return getFolder().toString();
}
diff --git a/src/main/java/land/oras/OCI.java b/src/main/java/land/oras/OCI.java
index 25312bd7..00c2ba77 100644
--- a/src/main/java/land/oras/OCI.java
+++ b/src/main/java/land/oras/OCI.java
@@ -20,6 +20,7 @@
package land.oras;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -36,6 +37,7 @@
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import land.oras.exception.OrasException;
import land.oras.utils.ArchiveUtils;
import land.oras.utils.Const;
@@ -51,7 +53,12 @@
* Commons methods for OCI operations
* @param The reference type
*/
-public abstract sealed class OCI> permits Registry, OCILayout {
+@JsonAutoDetect(
+ getterVisibility = JsonAutoDetect.Visibility.NONE,
+ isGetterVisibility = JsonAutoDetect.Visibility.NONE,
+ setterVisibility = JsonAutoDetect.Visibility.NONE,
+ fieldVisibility = JsonAutoDetect.Visibility.NONE)
+public abstract class OCI> {
/**
* The logger
@@ -342,7 +349,7 @@ protected final List pushLayers(
.map(p -> CompletableFuture.supplyAsync(
() -> pushLayer(ref, annotations, withDigest, p, options), getExecutorService()))
.map(CompletableFuture::join)
- .toList();
+ .collect(Collectors.toList());
} catch (CompletionException e) {
throw new OrasException("Failed to push layers", e.getCause());
}
@@ -650,7 +657,7 @@ protected Layer pushLayer(T ref, Annotations annotations, boolean withDigest, Lo
// We store the filename, based on directory name if we don't auto unpack
if (!autoUnpack) {
- title = "%s.%s".formatted(title, compression.getFileExtension());
+ title = String.format("%s.%s", title, compression.getFileExtension());
}
LOG.debug("Uploading directory as archive with title: {}", title);
diff --git a/src/main/java/land/oras/OCILayout.java b/src/main/java/land/oras/OCILayout.java
index df315759..124eb056 100644
--- a/src/main/java/land/oras/OCILayout.java
+++ b/src/main/java/land/oras/OCILayout.java
@@ -36,6 +36,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import land.oras.OCI.PullOptions;
import land.oras.OCI.PushOptions;
import land.oras.exception.OrasException;
@@ -103,7 +104,7 @@ public boolean mountBlob(LayoutRef sourceRef, LayoutRef targetRef) {
.resolve(algorithm.getPrefix())
.resolve(SupportedAlgorithm.getDigest(digest));
if (!Files.exists(sourceBlobPath)) {
- throw new OrasException("Source blob not found at: %s".formatted(sourceBlobPath));
+ throw new OrasException(String.format("Source blob not found at: %s", sourceBlobPath));
}
try {
Files.copy(sourceBlobPath, targetBlobPath);
@@ -187,8 +188,9 @@ public void pullArtifact(LayoutRef ref, Path path, PullOptions options) {
Path targetPath = path.resolve(layer.getAnnotations().get(Const.ANNOTATION_TITLE))
.normalize();
if (!targetPath.startsWith(path.normalize())) {
- throw new OrasException("Refusing to pull layer: title annotation is not withing folder '%s'"
- .formatted(layer.getAnnotations().get(Const.ANNOTATION_TITLE)));
+ throw new OrasException(String.format(
+ "Refusing to pull layer: title annotation is not withing folder '%s'",
+ layer.getAnnotations().get(Const.ANNOTATION_TITLE)));
}
if (options.isOverwrite()) {
Files.copy(blobPath, targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
@@ -291,7 +293,7 @@ public Manifest getManifest(LayoutRef ref) {
ManifestDescriptor descriptor = findManifestDescriptor(ref);
Path manifestPath = getBlobPath(descriptor);
if (!Files.exists(manifestPath)) {
- throw new OrasException("Blob not found: %s".formatted(manifestPath));
+ throw new OrasException(String.format("Blob not found: %s", manifestPath));
}
return Manifest.fromPath(manifestPath).withDescriptor(descriptor);
@@ -336,16 +338,17 @@ void verifyBlobDigest(Path blobPath) {
Path fileName = blobPath.getFileName();
Path parent = blobPath.getParent();
if (fileName == null || parent == null || parent.getFileName() == null) {
- throw new OrasException("Cannot resolve expected digest for blob path: %s".formatted(blobPath));
+ throw new OrasException(String.format("Cannot resolve expected digest for blob path: %s", blobPath));
}
- String expectedDigest = "%s:%s".formatted(parent.getFileName(), fileName);
+ String expectedDigest = String.format("%s:%s", parent.getFileName(), fileName);
if (!SupportedAlgorithm.isSupported(expectedDigest)) {
- throw new OrasException("Blob is not stored at a content-addressed path: %s".formatted(blobPath));
+ throw new OrasException(String.format("Blob is not stored at a content-addressed path: %s", blobPath));
}
String actualDigest = SupportedAlgorithm.fromDigest(expectedDigest).digest(blobPath);
if (!expectedDigest.equals(actualDigest)) {
- throw new OrasException("Blob integrity check failed for %s: expected %s but on-disk content hashes to %s"
- .formatted(blobPath, expectedDigest, actualDigest));
+ throw new OrasException(String.format(
+ "Blob integrity check failed for %s: expected %s but on-disk content hashes to %s",
+ blobPath, expectedDigest, actualDigest));
}
}
@@ -369,7 +372,7 @@ public Layer pushBlob(LayoutRef ref, Path blob, Map annotations)
throw new OrasException("Missing ref");
}
if (!SupportedAlgorithm.isSupported(ref.getTag())) {
- throw new OrasException("Unsupported digest: %s".formatted(ref.getTag()));
+ throw new OrasException(String.format("Unsupported digest: %s", ref.getTag()));
}
String digest = ref.getTag();
ensureAlgorithmPath(digest);
@@ -399,7 +402,7 @@ public Layer pushBlob(LayoutRef ref, long size, Supplier stream, Ma
}
boolean isDigest = SupportedAlgorithm.isSupported(digest);
if (!isDigest) {
- throw new OrasException("Unsupported digest: %s".formatted(digest));
+ throw new OrasException(String.format("Unsupported digest: %s", digest));
}
ensureAlgorithmPath(digest);
try {
@@ -427,11 +430,11 @@ public Layer pushBlob(LayoutRef ref, byte[] data) {
throw new OrasException("Missing ref");
}
if (!SupportedAlgorithm.isSupported(ref.getTag())) {
- throw new OrasException("Unsupported digest: %s".formatted(ref.getTag()));
+ throw new OrasException(String.format("Unsupported digest: %s", ref.getTag()));
}
String digest = ref.getAlgorithm().digest(data);
if (!ref.getTag().equals(digest)) {
- throw new OrasException("Digest mismatch: %s != %s".formatted(ref.getTag(), digest));
+ throw new OrasException(String.format("Digest mismatch: %s != %s", ref.getTag(), digest));
}
ensureAlgorithmPath(digest);
Path blobPath = getBlobAlgorithmPath(digest).resolve(SupportedAlgorithm.getDigest(digest));
@@ -456,7 +459,7 @@ public Tags getTags(LayoutRef ref) {
.filter(m -> m.getAnnotations() != null && m.getAnnotations().containsKey(Const.ANNOTATION_REF))
.map(m -> m.getAnnotations().get(Const.ANNOTATION_REF))
.sorted()
- .toList();
+ .collect(Collectors.toList());
return new Tags(name, tags);
}
@@ -469,10 +472,10 @@ public Tags getTags(LayoutRef ref, int n, @Nullable String last) {
if (last != null) {
int lastIndex = tags.indexOf(last);
if (lastIndex == -1) {
- throw new OrasException("Last tag not found: %s".formatted(last));
+ throw new OrasException(String.format("Last tag not found: %s", last));
}
}
- return new Tags(name, tags.stream().skip(startIndex).limit(n).toList());
+ return new Tags(name, tags.stream().skip(startIndex).limit(n).collect(Collectors.toList()));
}
@Override
@@ -732,7 +735,7 @@ private ManifestDescriptor findManifestDescriptor(LayoutRef ref) {
&& tag.equals(m.getAnnotations().get(Const.ANNOTATION_REF))
|| tag.equals(m.getDigest())))
.findFirst()
- .orElseThrow(() -> new OrasException("Tag or digest not found: %s".formatted(tag)));
+ .orElseThrow(() -> new OrasException(String.format("Tag or digest not found: %s", tag)));
}
private Path getBlobPath(ManifestDescriptor manifestDescriptor) {
@@ -823,12 +826,12 @@ private void ensureDigest(LayoutRef ref, Path path) {
throw new OrasException("Missing ref");
}
if (!SupportedAlgorithm.isSupported(ref.getTag())) {
- throw new OrasException("Unsupported digest: %s".formatted(ref.getTag()));
+ throw new OrasException(String.format("Unsupported digest: %s", ref.getTag()));
}
SupportedAlgorithm algorithm = SupportedAlgorithm.fromDigest(ref.getTag());
String pathDigest = algorithm.digest(path);
if (!ref.getTag().equals(pathDigest)) {
- throw new OrasException("Digest mismatch: %s != %s".formatted(ref.getTag(), pathDigest));
+ throw new OrasException(String.format("Digest mismatch: %s != %s", ref.getTag(), pathDigest));
}
}
diff --git a/src/main/java/land/oras/Platform.java b/src/main/java/land/oras/Platform.java
index ca9a79b4..be9495b9 100644
--- a/src/main/java/land/oras/Platform.java
+++ b/src/main/java/land/oras/Platform.java
@@ -20,6 +20,7 @@
package land.oras;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@@ -30,13 +31,7 @@
import org.jspecify.annotations.Nullable;
/**
- * Record for platform information
- * @param os The operating system of the platform
- * @param architecture The architecture of the platform
- * @param variant The variant of the platform, which is optional and may be null
- * @param osVersion The operating system version of the platform, which is optional and may be
- * @param features The features of the platform, which is optional and may be null
- * @param osFeatures The operating system features of the platform, which is optional and may be
+ * Platform information for an OCI manifest
*/
@NullMarked
@OrasModel
@@ -48,13 +43,50 @@
Const.PLATFORM_OS_FEATURES
})
@JsonInclude(JsonInclude.Include.NON_NULL)
-public record Platform(
- @Nullable @JsonProperty(Const.PLATFORM_OS) String os,
- @Nullable @JsonProperty(Const.PLATFORM_ARCHITECTURE) String architecture,
- @Nullable @JsonProperty(Const.PLATFORM_OS_VERSION) String osVersion,
- @Nullable @JsonProperty(Const.PLATFORM_VARIANT) String variant,
- @Nullable @JsonProperty(Const.PLATFORM_FEATURES) List features,
- @Nullable @JsonProperty(Const.PLATFORM_OS_FEATURES) List osFeatures) {
+public final class Platform {
+
+ @Nullable
+ private final String os;
+
+ @Nullable
+ private final String architecture;
+
+ @Nullable
+ private final String osVersion;
+
+ @Nullable
+ private final String variant;
+
+ @Nullable
+ private final List features;
+
+ @Nullable
+ private final List osFeatures;
+
+ /**
+ * Create a new platform
+ * @param os The operating system
+ * @param architecture The architecture
+ * @param osVersion The os version
+ * @param variant The variant
+ * @param features The features
+ * @param osFeatures The os features
+ */
+ @JsonCreator
+ public Platform(
+ @Nullable @JsonProperty(Const.PLATFORM_OS) String os,
+ @Nullable @JsonProperty(Const.PLATFORM_ARCHITECTURE) String architecture,
+ @Nullable @JsonProperty(Const.PLATFORM_OS_VERSION) String osVersion,
+ @Nullable @JsonProperty(Const.PLATFORM_VARIANT) String variant,
+ @Nullable @JsonProperty(Const.PLATFORM_FEATURES) List features,
+ @Nullable @JsonProperty(Const.PLATFORM_OS_FEATURES) List osFeatures) {
+ this.os = os;
+ this.architecture = architecture;
+ this.osVersion = osVersion;
+ this.variant = variant;
+ this.features = features;
+ this.osFeatures = osFeatures;
+ }
/**
* Create a new platform linux/amd64
@@ -169,7 +201,7 @@ public static Platform of(String os, String architecture, @Nullable String varia
* Return the os of the platform, or "unknown" if the os is null
* @return The os of the platform
*/
- @Override
+ @JsonProperty(Const.PLATFORM_OS)
public String os() {
return os != null ? os : Const.PLATFORM_UNKNOWN;
}
@@ -178,11 +210,51 @@ public String os() {
* Return the os of the platform, or "unknown" if the os is null
* @return The os of the platform
*/
- @Override
+ @JsonProperty(Const.PLATFORM_ARCHITECTURE)
public String architecture() {
return architecture != null ? architecture : Const.PLATFORM_UNKNOWN;
}
+ /**
+ * Return the os version of the platform
+ * @return The os version
+ */
+ @Nullable
+ @JsonProperty(Const.PLATFORM_OS_VERSION)
+ public String osVersion() {
+ return osVersion;
+ }
+
+ /**
+ * Return the variant of the platform
+ * @return The variant
+ */
+ @Nullable
+ @JsonProperty(Const.PLATFORM_VARIANT)
+ public String variant() {
+ return variant;
+ }
+
+ /**
+ * Return the features of the platform
+ * @return The features
+ */
+ @Nullable
+ @JsonProperty(Const.PLATFORM_FEATURES)
+ public List features() {
+ return features;
+ }
+
+ /**
+ * Return the os features of the platform
+ * @return The os features
+ */
+ @Nullable
+ @JsonProperty(Const.PLATFORM_OS_FEATURES)
+ public List osFeatures() {
+ return osFeatures;
+ }
+
/**
* Create a new platform with the given features
* @param features The features of the platform
@@ -257,4 +329,28 @@ public static boolean matches(Platform platform, Platform target, boolean includ
}
return true;
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Platform)) return false;
+ Platform platform = (Platform) o;
+ return Objects.equals(os, platform.os)
+ && Objects.equals(architecture, platform.architecture)
+ && Objects.equals(osVersion, platform.osVersion)
+ && Objects.equals(variant, platform.variant)
+ && Objects.equals(features, platform.features)
+ && Objects.equals(osFeatures, platform.osFeatures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(os, architecture, osVersion, variant, features, osFeatures);
+ }
+
+ @Override
+ public String toString() {
+ return "Platform[os=" + os + ", architecture=" + architecture + ", osVersion=" + osVersion + ", variant="
+ + variant + ", features=" + features + ", osFeatures=" + osFeatures + "]";
+ }
}
diff --git a/src/main/java/land/oras/Ref.java b/src/main/java/land/oras/Ref.java
index 19755084..2d8f853e 100644
--- a/src/main/java/land/oras/Ref.java
+++ b/src/main/java/land/oras/Ref.java
@@ -29,7 +29,7 @@
* @param The type of the ref
*/
@NullMarked
-public abstract sealed class Ref> permits ContainerRef, LayoutRef {
+public abstract class Ref> {
/**
* The tag of the container.
diff --git a/src/main/java/land/oras/Registry.java b/src/main/java/land/oras/Registry.java
index 376b617c..1404e7e7 100644
--- a/src/main/java/land/oras/Registry.java
+++ b/src/main/java/land/oras/Registry.java
@@ -35,7 +35,6 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -45,6 +44,7 @@
import java.util.concurrent.Executors;
import java.util.function.BiFunction;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
import land.oras.auth.AuthProvider;
import land.oras.auth.AuthStoreAuthenticationProvider;
import land.oras.auth.BearerTokenProvider;
@@ -175,10 +175,11 @@ private Registry() {
@Override
public boolean canMount(OCI> other, ContainerRef sourceRef, ContainerRef targetRef) {
- if (!(other instanceof Registry otherRegistry)) {
+ if (!(other instanceof Registry)) {
LOG.debug("Other OCI is not a registry, cannot mount");
return false;
}
+ Registry otherRegistry = (Registry) other;
// Not the same registry
String effectiveSourceRegistry = sourceRef.getEffectiveRegistry(this);
String effectiveTargetRegistry = targetRef.getEffectiveRegistry(otherRegistry);
@@ -218,7 +219,7 @@ public boolean mountBlob(ContainerRef sourceRef, ContainerRef targetRef) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).mountBlob(sourceRef, ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsMountPath(this, sourceRef)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getBlobsMountPath(this, sourceRef)));
HttpClient.ResponseWrapper response = client.post(
uri,
new byte[0],
@@ -407,7 +408,7 @@ private Registry build() {
if (executorService == null) {
executorService = Executors.newFixedThreadPool(maxConcurrentDownloads, r -> {
Thread t = new Thread(r);
- t.setName("layer-transfer-worker-%d".formatted(t.getId()));
+ t.setName(String.format("layer-transfer-worker-%d", t.getId()));
return t;
});
}
@@ -530,7 +531,7 @@ public Tags getTags(ContainerRef containerRef) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).getTags(ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getTagsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getTagsPath(this)));
HttpClient.ResponseWrapper response = client.get(
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -547,9 +548,9 @@ public Tags getTags(ContainerRef containerRef) {
Integer n = page.n();
for (int pageNum = 1; last != null; pageNum++) {
if (tagListMaxPages > 0 && pageNum >= tagListMaxPages) {
- throw new OrasException(
- "Tag listing exceeded %d pages: possible self-referential Link header from a rogue registry"
- .formatted(tagListMaxPages));
+ throw new OrasException(String.format(
+ "Tag listing exceeded %d pages: possible self-referential Link header from a rogue registry",
+ tagListMaxPages));
}
Tags nextPage = getTags(ref, n == null ? Integer.MAX_VALUE : n, last);
@@ -568,7 +569,7 @@ public Tags getTags(ContainerRef containerRef, int n, @Nullable String last) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).getTags(ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getTagsPath(this, n, last)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getTagsPath(this, n, last)));
HttpClient.ResponseWrapper response = client.get(
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -593,7 +594,7 @@ && getRegistriesConf()
return asSecure().getRepositories();
}
ContainerRef ref = ContainerRef.parse("default").forRegistry(this);
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getRepositoriesPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getRepositoriesPath(this)));
HttpClient.ResponseWrapper response = client.get(
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_JSON_MEDIA_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -613,7 +614,7 @@ public Referrers getReferrers(ContainerRef containerRef, @Nullable ArtifactType
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).getReferrers(ref, artifactType);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getReferrersPath(this, artifactType)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getReferrersPath(this, artifactType)));
HttpClient.ResponseWrapper response = client.get(
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -630,11 +631,12 @@ public Referrers getReferrers(ContainerRef containerRef, @Nullable ArtifactType
List allManifests = new ArrayList<>(page.getManifests());
for (int pageNum = 1; last != null; pageNum++) {
if (referrerListMaxPages > 0 && pageNum >= referrerListMaxPages) {
- throw new OrasException(
- "Referrer listing exceeded %d pages: possible self-referential Link header from a rogue registry"
- .formatted(referrerListMaxPages));
+ throw new OrasException(String.format(
+ "Referrer listing exceeded %d pages: possible self-referential Link header from a rogue registry",
+ referrerListMaxPages));
}
- URI nextUri = URI.create("%s://%s".formatted(getScheme(), ref.getReferrersPath(this, artifactType, last)));
+ URI nextUri =
+ URI.create(String.format("%s://%s", getScheme(), ref.getReferrersPath(this, artifactType, last)));
HttpClient.ResponseWrapper nextResponse = client.get(
nextUri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE), Scopes.of(ref), authProvider);
logResponse(nextResponse);
@@ -655,7 +657,7 @@ public Referrers getReferrers(ContainerRef containerRef, @Nullable ArtifactType
*/
private Referrers getReferrersFallback(ContainerRef ref, @Nullable ArtifactType artifactType) {
ContainerRef fallbackRef = ref.withReferrersFallbackTag();
- URI uri = URI.create("%s://%s".formatted(getScheme(), fallbackRef.getManifestsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), fallbackRef.getManifestsPath(this)));
HttpClient.ResponseWrapper response = client.get(
uri, Map.of(Const.ACCEPT_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE), Scopes.of(fallbackRef), authProvider);
logResponse(response);
@@ -672,7 +674,7 @@ private Referrers getReferrersFallback(ContainerRef ref, @Nullable ArtifactType
if (artifactType != null) {
manifests = manifests.stream()
.filter(manifest -> artifactType.getMediaType().equals(manifest.getArtifactType()))
- .toList();
+ .collect(Collectors.toList());
}
return Referrers.from(manifests);
}
@@ -691,7 +693,7 @@ public void deleteManifest(ContainerRef containerRef) {
asSecure().deleteManifest(containerRef);
return;
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getManifestsPath(this)));
HttpClient.ResponseWrapper response = client.delete(uri, Map.of(), Scopes.of(ref), authProvider);
logResponse(response);
handleError(response);
@@ -717,7 +719,7 @@ public Manifest pushManifest(ContainerRef containerRef, Manifest manifest) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).pushManifest(ref, manifest);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getManifestsPath(this)));
byte[] manifestData = manifest.getJson() != null
? manifest.getJson().getBytes()
: manifest.toJson().getBytes();
@@ -761,7 +763,7 @@ public Index pushIndex(ContainerRef containerRef, Index index) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).pushIndex(ref, index);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getManifestsPath(this)));
byte[] indexData = JsonUtils.toJson(index).getBytes();
LOG.debug("Index data to push: {}", new String(indexData, StandardCharsets.UTF_8));
HttpClient.ResponseWrapper response = client.put(
@@ -789,7 +791,7 @@ public void deleteBlob(ContainerRef containerRef) {
asSecure().deleteBlob(containerRef);
return;
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getBlobsPath(this)));
HttpClient.ResponseWrapper response = client.delete(uri, Map.of(), Scopes.of(ref), authProvider);
logResponse(response);
handleError(response);
@@ -900,7 +902,7 @@ public Layer pushBlob(ContainerRef containerRef, Path blob, Map
return Layer.fromFile(blob, ref.getAlgorithm()).withAnnotations(annotations);
}
URI uri = URI.create(
- "%s://%s".formatted(getScheme(), ref.withDigest(digest).getBlobsUploadDigestPath(this)));
+ String.format("%s://%s", getScheme(), ref.withDigest(digest).getBlobsUploadDigestPath(this)));
HttpClient.ResponseWrapper response = client.upload(
"POST",
uri,
@@ -920,8 +922,8 @@ public Layer pushBlob(ContainerRef containerRef, Path blob, Map
String location = response.headers().get(Const.LOCATION_HEADER.toLowerCase());
// Ensure location is absolute URI
if (!location.startsWith("http") && !location.startsWith("https")) {
- location =
- "%s://%s/%s".formatted(getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
+ location = String.format(
+ "%s://%s/%s", getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
}
LOG.debug("Location header: {}", location);
@@ -937,7 +939,7 @@ public Layer pushBlob(ContainerRef containerRef, Path blob, Map
if (response.statusCode() == 201) {
LOG.debug("Successful push: {}", response.response());
} else {
- throw new OrasException("Failed to push layer: %s".formatted(response.response()));
+ throw new OrasException(String.format("Failed to push layer: %s", response.response()));
}
}
@@ -963,7 +965,7 @@ public Layer pushBlob(ContainerRef ref, long size, Supplier stream,
return Layer.fromDigest(digest, size).withAnnotations(annotations);
}
// Empty post without digest
- URI uri = URI.create("%s://%s".formatted(getScheme(), containerRef.getBlobsUploadPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), containerRef.getBlobsUploadPath(this)));
HttpClient.ResponseWrapper response = client.post(
uri,
new byte[0],
@@ -972,12 +974,13 @@ public Layer pushBlob(ContainerRef ref, long size, Supplier stream,
authProvider);
logResponse(response);
if (response.statusCode() != 202) {
- throw new OrasException("Failed to initiate blob upload: %s".formatted(response.response()));
+ throw new OrasException(String.format("Failed to initiate blob upload: %s", response.response()));
}
String location = response.headers().get(Const.LOCATION_HEADER.toLowerCase());
// Ensure location is absolute URI
if (!location.startsWith("http") && !location.startsWith("https")) {
- location = "%s://%s/%s".formatted(getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
+ location =
+ String.format("%s://%s/%s", getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
}
LOG.debug("Location header: {}", location);
@@ -994,7 +997,7 @@ public Layer pushBlob(ContainerRef ref, long size, Supplier stream,
if (response.statusCode() == 201) {
LOG.debug("Successful push: {}", response.response());
} else {
- throw new OrasException("Failed to push layer: %s".formatted(response.response()));
+ throw new OrasException(String.format("Failed to push layer: %s", response.response()));
}
handleError(response);
return Layer.fromDigest(digest, size).withAnnotations(annotations);
@@ -1018,7 +1021,7 @@ public Layer pushBlob(ContainerRef containerRef, byte[] data) {
return Layer.fromData(ref, data);
}
URI uri = URI.create(
- "%s://%s".formatted(getScheme(), ref.withDigest(digest).getBlobsUploadDigestPath(this)));
+ String.format("%s://%s", getScheme(), ref.withDigest(digest).getBlobsUploadDigestPath(this)));
HttpClient.ResponseWrapper response = client.post(
uri,
data,
@@ -1037,8 +1040,8 @@ public Layer pushBlob(ContainerRef containerRef, byte[] data) {
String location = response.headers().get(Const.LOCATION_HEADER.toLowerCase());
// Ensure location is absolute URI
if (!location.startsWith("http") && !location.startsWith("https")) {
- location =
- "%s://%s/%s".formatted(getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
+ location = String.format(
+ "%s://%s/%s", getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
}
URI uploadURI = createLocationWithDigest(location, digest);
@@ -1053,7 +1056,7 @@ public Layer pushBlob(ContainerRef containerRef, byte[] data) {
if (response.statusCode() == 201) {
LOG.debug("Successful push: {}", response.response());
} else {
- throw new OrasException("Failed to push layer: %s".formatted(response.response()));
+ throw new OrasException(String.format("Failed to push layer: %s", response.response()));
}
}
@@ -1091,7 +1094,7 @@ public Layer pushBlobChunked(ContainerRef containerRef, Path blob, long chunkSiz
long totalSize = Files.size(blob);
location = uploadChunks(ref, is, totalSize, chunkSize, location);
} catch (IOException e) {
- throw new OrasException("Failed to read blob for chunked upload: %s".formatted(blob), e);
+ throw new OrasException(String.format("Failed to read blob for chunked upload: %s", blob), e);
}
finalizeChunkedUpload(ref, location, digest);
return Layer.fromFile(blob, ref.getAlgorithm());
@@ -1133,7 +1136,7 @@ public Layer pushBlobChunked(ContainerRef containerRef, InputStream stream, long
}
private String initiateChunkedUpload(ContainerRef ref) {
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsUploadPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getBlobsUploadPath(this)));
HttpClient.ResponseWrapper response = client.post(
uri,
new byte[0],
@@ -1143,11 +1146,12 @@ private String initiateChunkedUpload(ContainerRef ref) {
logResponse(response);
if (response.statusCode() != 202) {
throw new OrasException(
- "Failed to initiate chunked blob upload: status %d".formatted(response.statusCode()));
+ String.format("Failed to initiate chunked blob upload: status %d", response.statusCode()));
}
String location = response.headers().get(Const.LOCATION_HEADER.toLowerCase());
if (!location.startsWith("http://") && !location.startsWith("https://")) {
- location = "%s://%s/%s".formatted(getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
+ location =
+ String.format("%s://%s/%s", getScheme(), ref.getApiRegistry(this), location.replaceFirst("^/", ""));
}
LOG.debug("Chunked upload session location: {}", location);
return location;
@@ -1165,7 +1169,7 @@ private String uploadChunks(ContainerRef ref, InputStream stream, long totalSize
break;
}
long rangeEnd = offset + read - 1;
- String contentRange = "%d-%d".formatted(offset, rangeEnd);
+ String contentRange = String.format("%d-%d", offset, rangeEnd);
final byte[] chunk = java.util.Arrays.copyOf(buffer, read);
URI patchUri = URI.create(location);
HttpClient.ResponseWrapper patchResponse = client.patch(
@@ -1181,15 +1185,17 @@ private String uploadChunks(ContainerRef ref, InputStream stream, long totalSize
authProvider);
logResponse(patchResponse);
if (patchResponse.statusCode() != 202) {
- throw new OrasException("Chunked upload PATCH failed for range %s: status %d"
- .formatted(contentRange, patchResponse.statusCode()));
+ throw new OrasException(String.format(
+ "Chunked upload PATCH failed for range %s: status %d",
+ contentRange, patchResponse.statusCode()));
}
// The registry MAY return a new location after each PATCH
String newLocation = patchResponse.headers().get(Const.LOCATION_HEADER.toLowerCase());
if (newLocation != null && !newLocation.isBlank()) {
if (!newLocation.startsWith("http://") && !newLocation.startsWith("https://")) {
- newLocation = "%s://%s/%s"
- .formatted(getScheme(), ref.getApiRegistry(this), newLocation.replaceFirst("^/", ""));
+ newLocation = String.format(
+ "%s://%s/%s",
+ getScheme(), ref.getApiRegistry(this), newLocation.replaceFirst("^/", ""));
}
location = newLocation;
LOG.debug("Chunked upload location updated: {}", location);
@@ -1214,7 +1220,7 @@ private void finalizeChunkedUpload(ContainerRef ref, String location, String dig
logResponse(putResponse);
if (putResponse.statusCode() != 201) {
throw new OrasException(
- "Failed to finalize chunked blob upload: status %d".formatted(putResponse.statusCode()));
+ String.format("Failed to finalize chunked blob upload: status %d", putResponse.statusCode()));
}
LOG.debug("Chunked upload finalized successfully for digest: {}", digest);
}
@@ -1237,7 +1243,7 @@ private HttpClient.ResponseWrapper headBlob(ContainerRef containerRef) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).headBlob(ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getBlobsPath(this)));
HttpClient.ResponseWrapper response = client.head(
uri,
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
@@ -1283,7 +1289,7 @@ private void fetchBlobDirect(ContainerRef containerRef, Path path) {
asSecure().fetchBlobDirect(containerRef, path);
return;
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getBlobsPath(this)));
HttpClient.ResponseWrapper response = client.download(
uri,
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
@@ -1308,7 +1314,7 @@ private InputStream fetchBlobDirect(ContainerRef containerRef) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).fetchBlobDirect(ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getBlobsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getBlobsPath(this)));
HttpClient.ResponseWrapper response = client.download(
uri,
Map.of(Const.ACCEPT_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE),
@@ -1360,9 +1366,9 @@ public Manifest getManifest(ContainerRef containerRef) {
"Expected manifest but got index. Probably a multi-platform image instead of artifact");
}
if (contentType.equals(Const.LEGACY_MANIFEST_MEDIA_TYPE)) {
- throw new OrasException(
- "Schema version 1 with manifest media type '%s' is not supported. Please use schema version 2 or higher"
- .formatted(contentType));
+ throw new OrasException(String.format(
+ "Schema version 1 with manifest media type '%s' is not supported. Please use schema version 2 or higher",
+ contentType));
}
String json = descriptor.getJson();
String digest = descriptor.getDigest();
@@ -1388,7 +1394,7 @@ public Index getIndex(ContainerRef containerRef) {
Descriptor descriptor = getDescriptor(containerRef);
String contentType = descriptor.getMediaType();
if (!isIndexMediaType(contentType)) {
- throw new OrasException("Expected index but got %s".formatted(contentType));
+ throw new OrasException(String.format("Expected index but got %s", contentType));
}
String json = descriptor.getJson();
String digest = descriptor.getDigest();
@@ -1449,7 +1455,7 @@ boolean exists(ContainerRef containerRef) {
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).exists(ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getManifestsPath(this)));
HttpClient.ResponseWrapper response =
client.head(uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -1485,11 +1491,57 @@ boolean existsWithMirrorFallback(ContainerRef containerRef) {
/**
* A mirror registry paired with the container reference rewritten to point at it.
- * @param registry The registry pointed at the mirror's location
- * @param ref The container reference rewritten for the mirror
*/
@OrasModel
- private record MirrorCandidate(Registry registry, ContainerRef ref) {}
+ private static final class MirrorCandidate {
+
+ private final Registry registry;
+ private final ContainerRef ref;
+
+ /**
+ * Create a new MirrorCandidate.
+ * @param registry The registry pointed at the mirror's location
+ * @param ref The container reference rewritten for the mirror
+ */
+ MirrorCandidate(Registry registry, ContainerRef ref) {
+ this.registry = registry;
+ this.ref = ref;
+ }
+
+ /**
+ * Get the registry pointed at the mirror's location.
+ * @return The registry
+ */
+ Registry registry() {
+ return registry;
+ }
+
+ /**
+ * Get the container reference rewritten for the mirror.
+ * @return The container reference
+ */
+ ContainerRef ref() {
+ return ref;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MirrorCandidate)) return false;
+ MirrorCandidate that = (MirrorCandidate) o;
+ return Objects.equals(registry, that.registry) && Objects.equals(ref, that.ref);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(registry, ref);
+ }
+
+ @Override
+ public String toString() {
+ return "MirrorCandidate[registry=" + registry + ", ref=" + ref + "]";
+ }
+ }
/**
* Resolve the mirrors applicable to the given container reference, in configured order.
@@ -1557,7 +1609,7 @@ private HttpClient.ResponseWrapper getManifestResponseDirect(ContainerRe
if (!ref.isInsecure(this) && this.isInsecure()) {
return copyForNewTransport(ref.getRegistry(), false).getManifestResponseDirect(ref);
}
- URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this)));
+ URI uri = URI.create(String.format("%s://%s", getScheme(), ref.getManifestsPath(this)));
HttpClient.ResponseWrapper response =
client.head(uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -1627,7 +1679,7 @@ private void ensureDigest(String expected, @Nullable String current) {
throw new OrasException("Received null digest");
}
if (!expected.equals(current)) {
- throw new OrasException("Digest mismatch: %s != %s".formatted(expected, current));
+ throw new OrasException(String.format("Digest mismatch: %s != %s", expected, current));
}
}
@@ -1733,7 +1785,7 @@ private void pullLayer(ContainerRef ref, Layer layer, Path path, boolean overwri
LOG.trace("Actual digest: {}", actualDigest);
if (!expectedDigest.equals(actualDigest)) {
throw new OrasException(
- "Digest mismatch: expected %s but got %s".formatted(expectedDigest, actualDigest));
+ String.format("Digest mismatch: expected %s but got %s", expectedDigest, actualDigest));
}
}
@@ -1744,9 +1796,9 @@ private void pullLayer(ContainerRef ref, Layer layer, Path path, boolean overwri
Path targetPath = path.resolve(layer.getAnnotations().get(Const.ANNOTATION_TITLE))
.normalize();
if (!targetPath.startsWith(path.normalize())) {
- throw new OrasException(
- "Refusing to pull layer: path is not withing folder in title annotation '%s'"
- .formatted(layer.getAnnotations().get(Const.ANNOTATION_TITLE)));
+ throw new OrasException(String.format(
+ "Refusing to pull layer: path is not withing folder in title annotation '%s'",
+ layer.getAnnotations().get(Const.ANNOTATION_TITLE)));
}
if (Files.exists(targetPath) && !overwrite) {
LOG.info("File already exists: {}", targetPath);
@@ -1772,12 +1824,12 @@ URI createLocationWithDigest(String location, String digest) {
// sometimes CI can add a query to this, so making sure we're using the right query param symbol
if (uploadURI.getQuery() == null) {
- uploadURI = new URI(uploadURI + "?digest=%s".formatted(digest));
+ uploadURI = new URI(uploadURI + String.format("?digest=%s", digest));
} else {
- uploadURI = new URI(uploadURI + "&digest=%s".formatted(digest));
+ uploadURI = new URI(uploadURI + String.format("&digest=%s", digest));
}
} catch (URISyntaxException e) {
- throw new OrasException("Failed parse location header: %s".formatted(location));
+ throw new OrasException(String.format("Failed parse location header: %s", location));
}
return uploadURI;
@@ -1797,7 +1849,7 @@ ResolvedRegistry getResolvedHeaders(ContainerRef containerRef) {
return copyForNewTransport(ref.getRegistry(), false).getResolvedHeaders(ref);
}
URI uri = URI.create(
- "%s://%s".formatted(getScheme(), ref.forRegistry(this).getManifestsPath(this)));
+ String.format("%s://%s", getScheme(), ref.forRegistry(this).getManifestsPath(this)));
HttpClient.ResponseWrapper response =
client.head(uri, Map.of(Const.ACCEPT_HEADER, Const.MANIFEST_ACCEPT_TYPE), Scopes.of(ref), authProvider);
logResponse(response);
@@ -1848,10 +1900,56 @@ private Optional getParamFromLink(HttpClient.ResponseWrapper res
/**
* Holds a resolved registry to avoid resolution on every request (specially like blob)
- * @param registry The registry URL
- * @param headers The headers to use for the registry
*/
- private record ResolvedRegistry(String registry, Map headers) {}
+ private static final class ResolvedRegistry {
+
+ private final String registry;
+ private final Map headers;
+
+ /**
+ * Create a new ResolvedRegistry.
+ * @param registry The registry URL
+ * @param headers The headers to use for the registry
+ */
+ ResolvedRegistry(String registry, Map headers) {
+ this.registry = registry;
+ this.headers = headers;
+ }
+
+ /**
+ * Get the registry URL.
+ * @return The registry URL
+ */
+ String registry() {
+ return registry;
+ }
+
+ /**
+ * Get the headers to use for the registry.
+ * @return The headers
+ */
+ Map headers() {
+ return headers;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ResolvedRegistry)) return false;
+ ResolvedRegistry that = (ResolvedRegistry) o;
+ return Objects.equals(registry, that.registry) && Objects.equals(headers, that.headers);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(registry, headers);
+ }
+
+ @Override
+ public String toString() {
+ return "ResolvedRegistry[registry=" + registry + ", headers=" + headers + "]";
+ }
+ }
/**
* Builder for the registry
@@ -2222,15 +2320,24 @@ private void verify() {
}
verified = true;
Map actualByPrefix = new HashMap<>();
- digestsByPrefix.forEach((prefix, digest) ->
- actualByPrefix.put(prefix, prefix + ":" + HexFormat.of().formatHex(digest.digest())));
+ digestsByPrefix.forEach(
+ (prefix, digest) -> actualByPrefix.put(prefix, prefix + ":" + toHexString(digest.digest())));
for (String expected : expectedDigests) {
String prefix = SupportedAlgorithm.fromDigest(expected).getPrefix();
String actual = actualByPrefix.get(prefix);
if (!expected.equals(actual)) {
- throw new OrasException("Digest mismatch: %s != %s".formatted(expected, actual));
+ throw new OrasException(String.format("Digest mismatch: %s != %s", expected, actual));
}
}
}
}
+
+ private static String toHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ sb.append(Character.forDigit((b >> 4) & 0xF, 16));
+ sb.append(Character.forDigit(b & 0xF, 16));
+ }
+ return sb.toString();
+ }
}
diff --git a/src/main/java/land/oras/Repositories.java b/src/main/java/land/oras/Repositories.java
index bfe094bf..49a08aec 100644
--- a/src/main/java/land/oras/Repositories.java
+++ b/src/main/java/land/oras/Repositories.java
@@ -20,13 +20,54 @@
package land.oras;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
+import java.util.Objects;
import org.jspecify.annotations.NullMarked;
/**
* The repositories response object
- * @param repositories The repositories
*/
@NullMarked
@OrasModel
-public record Repositories(List repositories) {}
+public final class Repositories {
+
+ private final List repositories;
+
+ /**
+ * Create a new repositories instance
+ * @param repositories The repositories
+ */
+ @JsonCreator
+ public Repositories(@JsonProperty("repositories") List repositories) {
+ this.repositories = repositories;
+ }
+
+ /**
+ * Get the repositories
+ * @return The repositories
+ */
+ @JsonProperty("repositories")
+ public List repositories() {
+ return repositories;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Repositories)) return false;
+ Repositories that = (Repositories) o;
+ return Objects.equals(repositories, that.repositories);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(repositories);
+ }
+
+ @Override
+ public String toString() {
+ return "Repositories[repositories=" + repositories + "]";
+ }
+}
diff --git a/src/main/java/land/oras/Tags.java b/src/main/java/land/oras/Tags.java
index 588f24cc..1b11ae9b 100644
--- a/src/main/java/land/oras/Tags.java
+++ b/src/main/java/land/oras/Tags.java
@@ -20,22 +20,45 @@
package land.oras;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
+import java.util.Objects;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* The tags response object
- * @param name The name
- * @param tags The tags
- * @param last The last tag index, to iterate
- * @param n The n parameter, to iterate. As part of link response
*/
@NullMarked
@OrasModel
@JsonInclude(JsonInclude.Include.NON_NULL)
-public record Tags(String name, List tags, @Nullable String last, @Nullable Integer n) {
+public final class Tags {
+
+ private final String name;
+ private final List tags;
+ private final @Nullable String last;
+ private final @Nullable Integer n;
+
+ /**
+ * Constructor with all fields
+ * @param name The name
+ * @param tags The tags
+ * @param last The last tag index, to iterate
+ * @param n The n parameter, to iterate
+ */
+ @JsonCreator
+ public Tags(
+ @JsonProperty("name") String name,
+ @JsonProperty("tags") List tags,
+ @JsonProperty("last") @Nullable String last,
+ @JsonProperty("n") @Nullable Integer n) {
+ this.name = name;
+ this.tags = tags;
+ this.last = last;
+ this.n = n;
+ }
/**
* Constructor without last
@@ -46,6 +69,42 @@ public Tags(String name, List tags) {
this(name, tags, null, null);
}
+ /**
+ * Get the name
+ * @return The name
+ */
+ @JsonProperty("name")
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Get the tags
+ * @return The tags
+ */
+ @JsonProperty("tags")
+ public List tags() {
+ return tags;
+ }
+
+ /**
+ * Get the last tag index
+ * @return The last tag index
+ */
+ @JsonProperty("last")
+ public @Nullable String last() {
+ return last;
+ }
+
+ /**
+ * Get the n parameter
+ * @return The n parameter
+ */
+ @JsonProperty("n")
+ public @Nullable Integer n() {
+ return n;
+ }
+
/**
* With last
* @param last The last tag index, to iterate
@@ -63,4 +122,25 @@ public Tags withLast(@Nullable String last) {
public Tags withN(@Nullable Integer n) {
return new Tags(this.name, this.tags, last, n);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Tags)) return false;
+ Tags that = (Tags) o;
+ return Objects.equals(name, that.name)
+ && Objects.equals(tags, that.tags)
+ && Objects.equals(last, that.last)
+ && Objects.equals(n, that.n);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, tags, last, n);
+ }
+
+ @Override
+ public String toString() {
+ return "Tags[name=" + name + ", tags=" + tags + ", last=" + last + ", n=" + n + "]";
+ }
}
diff --git a/src/main/java/land/oras/auth/AbstractUsernamePasswordProvider.java b/src/main/java/land/oras/auth/AbstractUsernamePasswordProvider.java
index e95912a6..b207e38d 100644
--- a/src/main/java/land/oras/auth/AbstractUsernamePasswordProvider.java
+++ b/src/main/java/land/oras/auth/AbstractUsernamePasswordProvider.java
@@ -26,7 +26,7 @@
/**
* A provider for username and password authentication
*/
-public abstract sealed class AbstractUsernamePasswordProvider implements AuthProvider permits UsernamePasswordProvider {
+public abstract class AbstractUsernamePasswordProvider implements AuthProvider {
/**
* The username
diff --git a/src/main/java/land/oras/auth/AuthStore.java b/src/main/java/land/oras/auth/AuthStore.java
index e8726146..d764e7cb 100644
--- a/src/main/java/land/oras/auth/AuthStore.java
+++ b/src/main/java/land/oras/auth/AuthStore.java
@@ -20,6 +20,7 @@
package land.oras.auth;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.IOException;
import java.io.OutputStream;
@@ -66,9 +67,9 @@ public class AuthStore {
*/
static String credentialHelperBinaryName(@Nullable String suffix) {
if (suffix == null || !VALID_HELPER_SUFFIX.matcher(suffix).matches()) {
- throw new OrasException(
- "Invalid credential helper name '%s': only letters, digits, '_' and '-' are allowed (no path separators or '..')"
- .formatted(suffix));
+ throw new OrasException(String.format(
+ "Invalid credential helper name '%s': only letters, digits, '_' and '-' are allowed (no path separators or '..')",
+ suffix));
}
return "docker-credential-" + suffix;
}
@@ -162,15 +163,58 @@ private static List defaultAuthPaths() {
/**
* Nested ConfigFile class to represent the configuration file.
- * @param auths The auths map.
- * @param credHelpers The credential helpers map.
- * @param credsStore The credentials store.
*/
@OrasModel
- record ConfigFile(
- Map> auths,
- @Nullable Map credHelpers,
- @Nullable String credsStore) {
+ static final class ConfigFile {
+
+ private final Map> auths;
+ private final @Nullable Map credHelpers;
+ private final @Nullable String credsStore;
+
+ /**
+ * Constructs a new {@code ConfigFile} object.
+ * @param auths The auths map.
+ * @param credHelpers The credential helpers map.
+ * @param credsStore The credentials store.
+ */
+ @JsonCreator
+ ConfigFile(
+ @JsonProperty("auths") Map> auths,
+ @JsonProperty("credHelpers") @Nullable Map credHelpers,
+ @JsonProperty("credsStore") @Nullable String credsStore) {
+ this.auths = auths;
+ this.credHelpers = credHelpers;
+ this.credsStore = credsStore;
+ }
+
+ /**
+ * Returns the auths map.
+ * @return The auths map.
+ */
+ @JsonProperty("auths")
+ Map> auths() {
+ return auths;
+ }
+
+ /**
+ * Returns the credential helpers map.
+ * @return The credential helpers map.
+ */
+ @JsonProperty("credHelpers")
+ @Nullable
+ Map credHelpers() {
+ return credHelpers;
+ }
+
+ /**
+ * Returns the credentials store.
+ * @return The credentials store.
+ */
+ @JsonProperty("credsStore")
+ @Nullable
+ String credsStore() {
+ return credsStore;
+ }
/**
* Constructs a new {@code ConfigFile} object with the specified auths.
@@ -189,6 +233,26 @@ static ConfigFile fromCredential(Credential credential) {
Map.of(),
null);
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ConfigFile)) return false;
+ ConfigFile that = (ConfigFile) o;
+ return Objects.equals(auths, that.auths)
+ && Objects.equals(credHelpers, that.credHelpers)
+ && Objects.equals(credsStore, that.credsStore);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(auths, credHelpers, credsStore);
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigFile[auths=" + auths + ", credHelpers=" + credHelpers + ", credsStore=" + credsStore + "]";
+ }
}
/**
@@ -334,8 +398,9 @@ private static Credential getFromCredentialHelper(String suffix, String hostname
if (exit != 0) {
String stderr = new String(proc.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
String stdout = new String(proc.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
- String message = "Credential helper '%s' exited with code %d and error: '%s' and stdout '%s'."
- .formatted(binary, exit, stderr.trim(), stdout.trim());
+ String message = String.format(
+ "Credential helper '%s' exited with code %d and error: '%s' and stdout '%s'.",
+ binary, exit, stderr.trim(), stdout.trim());
LOG.warn(message);
throw new OrasException(message);
}
@@ -355,11 +420,13 @@ private static Credential getFromCredentialHelper(String suffix, String hostname
/**
* Nested Credential class to represent username and password pairs.
- * @param username The username for the credential.
- * @param password The password for the credential.
*/
@OrasModel
- public record Credential(String username, String password) {
+ public static final class Credential {
+
+ private final String username;
+ private final String password;
+
/**
* Constructs a new {@code Credential} object with the specified username and password.
*
@@ -370,19 +437,95 @@ public Credential(String username, String password) {
this.username = Objects.requireNonNull(username, "Username cannot be null");
this.password = Objects.requireNonNull(password, "Password cannot be null");
}
+
+ /**
+ * Returns the username.
+ * @return The username.
+ */
+ public String username() {
+ return username;
+ }
+
+ /**
+ * Returns the password.
+ * @return The password.
+ */
+ public String password() {
+ return password;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Credential)) return false;
+ Credential that = (Credential) o;
+ return Objects.equals(username, that.username) && Objects.equals(password, that.password);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(username, password);
+ }
+
+ @Override
+ public String toString() {
+ return "Credential[username=" + username + ", password=" + password + "]";
+ }
}
/**
- * Credential helper response
- * @param serverUrl The server URL
- * @param username The username
- * @param secret The secret (password or token)
+ * Credential helper response.
*/
@OrasModel
- public record CredentialHelperResponse(
- @JsonProperty("ServerURL") String serverUrl,
- @JsonProperty("Username") String username,
- @JsonProperty("Secret") String secret) {
+ public static final class CredentialHelperResponse {
+
+ private final String serverUrl;
+ private final String username;
+ private final String secret;
+
+ /**
+ * Constructs a new {@code CredentialHelperResponse}.
+ * @param serverUrl The server URL.
+ * @param username The username.
+ * @param secret The secret (password or token).
+ */
+ @JsonCreator
+ public CredentialHelperResponse(
+ @JsonProperty("ServerURL") String serverUrl,
+ @JsonProperty("Username") String username,
+ @JsonProperty("Secret") String secret) {
+ this.serverUrl = serverUrl;
+ this.username = username;
+ this.secret = secret;
+ }
+
+ /**
+ * Returns the server URL.
+ * @return The server URL.
+ */
+ @JsonProperty("ServerURL")
+ public String serverUrl() {
+ return serverUrl;
+ }
+
+ /**
+ * Returns the username.
+ * @return The username.
+ */
+ @JsonProperty("Username")
+ public String username() {
+ return username;
+ }
+
+ /**
+ * Returns the secret.
+ * @return The secret (password or token).
+ */
+ @JsonProperty("Secret")
+ public String secret() {
+ return secret;
+ }
+
/**
* Convert to Credential
* @return Credential
@@ -390,5 +533,26 @@ public record CredentialHelperResponse(
public Credential asCredential() {
return new Credential(username, secret);
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CredentialHelperResponse)) return false;
+ CredentialHelperResponse that = (CredentialHelperResponse) o;
+ return Objects.equals(serverUrl, that.serverUrl)
+ && Objects.equals(username, that.username)
+ && Objects.equals(secret, that.secret);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(serverUrl, username, secret);
+ }
+
+ @Override
+ public String toString() {
+ return "CredentialHelperResponse[serverUrl=" + serverUrl + ", username=" + username + ", secret=" + secret
+ + "]";
+ }
}
}
diff --git a/src/main/java/land/oras/auth/HttpClient.java b/src/main/java/land/oras/auth/HttpClient.java
index 47b9301f..676eee52 100644
--- a/src/main/java/land/oras/auth/HttpClient.java
+++ b/src/main/java/land/oras/auth/HttpClient.java
@@ -20,6 +20,8 @@
package land.oras.auth;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
@@ -605,7 +607,7 @@ public TokenResponse refreshToken(
LOG.debug("WWW-Authenticate header: realm={}, service={}, scope={}, error={}", realm, service, scope, error);
- String query = "scope=%s&service=%s".formatted(scope, URLEncoder.encode(service, StandardCharsets.UTF_8));
+ String query = String.format("scope=%s&service=%s", scope, URLEncoder.encode(service, StandardCharsets.UTF_8));
URI uri = URI.create(realm + "?" + query);
@@ -687,13 +689,16 @@ private ResponseWrapper executeRequest(
// Scopes are invariant across retries — compute once.
ContainerRef containerRef = scopes.getContainerRef();
LOG.debug("Scopes are adding registry scopes");
- Scopes newScopes =
- switch (method) {
- case "GET", "HEAD" -> scopes.withAddedRegistryScopes(Scope.PULL);
- case "POST", "PUT", "PATCH" -> scopes.withAddedRegistryScopes(Scope.PUSH);
- case "DELETE" -> scopes.withAddedRegistryScopes(Scope.DELETE);
- default -> throw new OrasException("Unsupported HTTP method: " + method);
- };
+ Scopes newScopes;
+ if ("GET".equals(method) || "HEAD".equals(method)) {
+ newScopes = scopes.withAddedRegistryScopes(Scope.PULL);
+ } else if ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method)) {
+ newScopes = scopes.withAddedRegistryScopes(Scope.PUSH);
+ } else if ("DELETE".equals(method)) {
+ newScopes = scopes.withAddedRegistryScopes(Scope.DELETE);
+ } else {
+ throw new OrasException("Unsupported HTTP method: " + method);
+ }
newScopes = newScopes.withIdentity(authProvider.getIdentity(containerRef));
LOG.debug("Existing scopes: {}", scopes.getScopes());
LOG.debug("New scopes: {}", newScopes.getScopes());
@@ -890,8 +895,25 @@ private ResponseWrapper redoRequest(
boolean includeAuthHeaderForRedirect = isSameOrigin(originUri, redirectUri);
if (!includeAuthHeaderForRedirect) {
LOG.debug("Skipping auth header for redirect from {} to {}", originUri, redirectUri);
- builder = HttpRequest.newBuilder(
- builder.build(), (name, value) -> !name.equalsIgnoreCase(Const.AUTHORIZATION_HEADER));
+ HttpRequest existingReq = builder.build();
+ HttpRequest.Builder redirectBuilder =
+ HttpRequest.newBuilder().uri(existingReq.uri());
+ existingReq.timeout().ifPresent(redirectBuilder::timeout);
+ existingReq.headers().map().forEach((headerName, values) -> {
+ if (!headerName.equalsIgnoreCase(Const.AUTHORIZATION_HEADER)) {
+ for (String v : values) {
+ redirectBuilder.header(headerName, v);
+ }
+ }
+ });
+ if (existingReq.bodyPublisher().isPresent()) {
+ redirectBuilder.method(
+ existingReq.method(),
+ existingReq.bodyPublisher().get());
+ } else {
+ redirectBuilder.method(existingReq.method(), HttpRequest.BodyPublishers.noBody());
+ }
+ builder = redirectBuilder;
}
return toResponseWrapper(
@@ -958,13 +980,81 @@ private void logRequest(HttpRequest request, byte[] body) {
/**
* Response wrapper
* @param The response type
- * @param response The response
- * @param statusCode The status code
- * @param headers The headers
- * @param service The service (not on response but on HTTP headers)
*/
- public record ResponseWrapper(
- T response, int statusCode, Map headers, @Nullable String service) {}
+ public static final class ResponseWrapper {
+ private final T response;
+ private final int statusCode;
+ private final Map headers;
+ private final @Nullable String service;
+
+ /**
+ * Create a new response wrapper
+ * @param response The response
+ * @param statusCode The status code
+ * @param headers The headers
+ * @param service The service (not on response but on HTTP headers)
+ */
+ public ResponseWrapper(T response, int statusCode, Map headers, @Nullable String service) {
+ this.response = response;
+ this.statusCode = statusCode;
+ this.headers = headers;
+ this.service = service;
+ }
+
+ /**
+ * Get the response
+ * @return The response
+ */
+ public T response() {
+ return response;
+ }
+
+ /**
+ * Get the status code
+ * @return The status code
+ */
+ public int statusCode() {
+ return statusCode;
+ }
+
+ /**
+ * Get the headers
+ * @return The headers
+ */
+ public Map headers() {
+ return headers;
+ }
+
+ /**
+ * Get the service
+ * @return The service
+ */
+ public @Nullable String service() {
+ return service;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ResponseWrapper> that = (ResponseWrapper>) o;
+ return statusCode == that.statusCode
+ && Objects.equals(response, that.response)
+ && Objects.equals(headers, that.headers)
+ && Objects.equals(service, that.service);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(response, statusCode, headers, service);
+ }
+
+ @Override
+ public String toString() {
+ return "ResponseWrapper[response=" + response + ", statusCode=" + statusCode + ", headers=" + headers
+ + ", service=" + service + "]";
+ }
+ }
/**
* Insecure trust manager when skipping TLS verification
@@ -996,19 +1086,81 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngi
/**
* The token response
- * @param token The token
- * @param service The service (not on response but on HTTP headers)
- * @param access_token The access token
- * @param expires_in The expires in
- * @param issued_at The issued at
*/
@OrasModel
- public record TokenResponse(
- String token,
- @Nullable String access_token,
- @Nullable String service,
- @Nullable Integer expires_in,
- @Nullable ZonedDateTime issued_at) {
+ public static final class TokenResponse {
+ private final String token;
+ private final @Nullable String access_token;
+ private final @Nullable String service;
+ private final @Nullable Integer expires_in;
+ private final @Nullable ZonedDateTime issued_at;
+
+ /**
+ * Create a new token response
+ * @param token The token
+ * @param access_token The access token
+ * @param service The service (not on response but on HTTP headers)
+ * @param expires_in The expires in
+ * @param issued_at The issued at
+ */
+ @JsonCreator
+ public TokenResponse(
+ @JsonProperty("token") String token,
+ @JsonProperty("access_token") @Nullable String access_token,
+ @JsonProperty("service") @Nullable String service,
+ @JsonProperty("expires_in") @Nullable Integer expires_in,
+ @JsonProperty("issued_at") @Nullable ZonedDateTime issued_at) {
+ this.token = token;
+ this.access_token = access_token;
+ this.service = service;
+ this.expires_in = expires_in;
+ this.issued_at = issued_at;
+ }
+
+ /**
+ * Get the token
+ * @return The token
+ */
+ @JsonProperty("token")
+ public String token() {
+ return token;
+ }
+
+ /**
+ * Get the access token
+ * @return The access token
+ */
+ @JsonProperty("access_token")
+ public @Nullable String access_token() {
+ return access_token;
+ }
+
+ /**
+ * Get the service
+ * @return The service
+ */
+ @JsonProperty("service")
+ public @Nullable String service() {
+ return service;
+ }
+
+ /**
+ * Get the expires in
+ * @return The expires in
+ */
+ @JsonProperty("expires_in")
+ public @Nullable Integer expires_in() {
+ return expires_in;
+ }
+
+ /**
+ * Get the issued at
+ * @return The issued at
+ */
+ @JsonProperty("issued_at")
+ public @Nullable ZonedDateTime issued_at() {
+ return issued_at;
+ }
/**
* Create a new token response with the service field set
@@ -1020,7 +1172,6 @@ public TokenResponse forService(String service) {
}
/**
- * >>>>>>> 6379975 (Store token into caffeine cache (#631))
* Get the effective token
* @return The effective token, which is either the access_token or the token field depending on which one is present
*/
@@ -1028,6 +1179,23 @@ public String getEffectiveToken() {
return access_token != null ? access_token : token;
}
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TokenResponse that = (TokenResponse) o;
+ return Objects.equals(token, that.token)
+ && Objects.equals(access_token, that.access_token)
+ && Objects.equals(service, that.service)
+ && Objects.equals(expires_in, that.expires_in)
+ && Objects.equals(issued_at, that.issued_at);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(token, access_token, service, expires_in, issued_at);
+ }
+
@Override
public String toString() {
return "TokenResponse{" + "expires_in=" + expires_in + ", issued_at=" + issued_at + '}';
diff --git a/src/main/java/land/oras/auth/RegistriesConf.java b/src/main/java/land/oras/auth/RegistriesConf.java
index 2dcbf1e1..878b13c7 100644
--- a/src/main/java/land/oras/auth/RegistriesConf.java
+++ b/src/main/java/land/oras/auth/RegistriesConf.java
@@ -145,15 +145,60 @@ private static List dropInFiles(Path dir) {
/**
* The model of a mirror entry within a [[registry]] table.
- * @param location The mirror registry location (host[:port][/path]).
- * @param insecure Whether the mirror is insecure.
- * @param pullFromMirror Controls which pull operations may use this mirror (default: {@link PullFromMirror#ALL}).
*/
@OrasModel
- public record MirrorConfig(
- @Nullable @JsonProperty("location") String location,
- @Nullable @JsonProperty("insecure") Boolean insecure,
- @Nullable @JsonProperty("pull-from-mirror") PullFromMirror pullFromMirror) {
+ public static final class MirrorConfig {
+
+ private final @Nullable String location;
+ private final @Nullable Boolean insecure;
+ private final @Nullable PullFromMirror pullFromMirror;
+
+ /**
+ * Constructor for MirrorConfig.
+ * @param location The mirror registry location (host[:port][/path]).
+ * @param insecure Whether the mirror is insecure.
+ * @param pullFromMirror Controls which pull operations may use this mirror (default: {@link PullFromMirror#ALL}).
+ */
+ @JsonCreator
+ public MirrorConfig(
+ @Nullable @JsonProperty("location") String location,
+ @Nullable @JsonProperty("insecure") Boolean insecure,
+ @Nullable @JsonProperty("pull-from-mirror") PullFromMirror pullFromMirror) {
+ this.location = location;
+ this.insecure = insecure;
+ this.pullFromMirror = pullFromMirror;
+ }
+
+ /**
+ * Return the mirror registry location.
+ * @return the mirror registry location
+ */
+ @Nullable
+ @JsonProperty("location")
+ public String location() {
+ return location;
+ }
+
+ /**
+ * Return whether the mirror is insecure.
+ * @return whether the mirror is insecure
+ */
+ @Nullable
+ @JsonProperty("insecure")
+ public Boolean insecure() {
+ return insecure;
+ }
+
+ /**
+ * Return the pull-from-mirror setting.
+ * @return the pull-from-mirror setting
+ */
+ @Nullable
+ @JsonProperty("pull-from-mirror")
+ public PullFromMirror pullFromMirror() {
+ return pullFromMirror;
+ }
+
/**
* Return true if this mirror should be accessed over plain HTTP or with unverified TLS.
* @return true if insecure
@@ -169,25 +214,127 @@ public boolean isInsecure() {
public PullFromMirror effectivePullFromMirror() {
return pullFromMirror != null ? pullFromMirror : PullFromMirror.ALL;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MirrorConfig)) return false;
+ MirrorConfig that = (MirrorConfig) o;
+ return Objects.equals(location, that.location)
+ && Objects.equals(insecure, that.insecure)
+ && Objects.equals(pullFromMirror, that.pullFromMirror);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(location, insecure, pullFromMirror);
+ }
+
+ @Override
+ public String toString() {
+ return "MirrorConfig[location=" + location + ", insecure=" + insecure + ", pullFromMirror=" + pullFromMirror
+ + "]";
+ }
}
/**
* The model of the registry configuration
- * @param prefix The prefix to match against container references.
- * @param location The registry location
- * @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images.
- * @param insecure Whether the registry is insecure. If true, the registry is considered insecure and may allow connections over HTTP or with invalid TLS certificates.
- * @param mirrorByDigestOnly If true, all mirrors for this registry are treated as digest-only (equivalent to setting pull-from-mirror=digest-only on every mirror).
- * @param mirrors Ordered list of mirror entries to try before the registry location.
*/
@OrasModel
- record RegistryConfig(
- @Nullable @JsonProperty("prefix") String prefix,
- @Nullable @JsonProperty("location") String location,
- @Nullable @JsonProperty("blocked") Boolean blocked,
- @Nullable @JsonProperty("insecure") Boolean insecure,
- @Nullable @JsonProperty("mirror-by-digest-only") Boolean mirrorByDigestOnly,
- @Nullable @JsonProperty("mirror") List mirrors) {
+ static final class RegistryConfig {
+
+ private final @Nullable String prefix;
+ private final @Nullable String location;
+ private final @Nullable Boolean blocked;
+ private final @Nullable Boolean insecure;
+ private final @Nullable Boolean mirrorByDigestOnly;
+ private final @Nullable List mirrors;
+
+ /**
+ * Constructor for RegistryConfig.
+ * @param prefix The prefix to match against container references.
+ * @param location The registry location
+ * @param blocked Whether the registry is blocked. If true, the registry is blocked and cannot be used for pulling or pushing images.
+ * @param insecure Whether the registry is insecure. If true, the registry is considered insecure and may allow connections over HTTP or with invalid TLS certificates.
+ * @param mirrorByDigestOnly If true, all mirrors for this registry are treated as digest-only (equivalent to setting pull-from-mirror=digest-only on every mirror).
+ * @param mirrors Ordered list of mirror entries to try before the registry location.
+ */
+ @JsonCreator
+ RegistryConfig(
+ @Nullable @JsonProperty("prefix") String prefix,
+ @Nullable @JsonProperty("location") String location,
+ @Nullable @JsonProperty("blocked") Boolean blocked,
+ @Nullable @JsonProperty("insecure") Boolean insecure,
+ @Nullable @JsonProperty("mirror-by-digest-only") Boolean mirrorByDigestOnly,
+ @Nullable @JsonProperty("mirror") List mirrors) {
+ this.prefix = prefix;
+ this.location = location;
+ this.blocked = blocked;
+ this.insecure = insecure;
+ this.mirrorByDigestOnly = mirrorByDigestOnly;
+ this.mirrors = mirrors;
+ }
+
+ /**
+ * Return the prefix to match against container references.
+ * @return the prefix
+ */
+ @Nullable
+ @JsonProperty("prefix")
+ String prefix() {
+ return prefix;
+ }
+
+ /**
+ * Return the registry location.
+ * @return the registry location
+ */
+ @Nullable
+ @JsonProperty("location")
+ String location() {
+ return location;
+ }
+
+ /**
+ * Return whether the registry is blocked.
+ * @return whether the registry is blocked
+ */
+ @Nullable
+ @JsonProperty("blocked")
+ Boolean blocked() {
+ return blocked;
+ }
+
+ /**
+ * Return whether the registry is insecure.
+ * @return whether the registry is insecure
+ */
+ @Nullable
+ @JsonProperty("insecure")
+ Boolean insecure() {
+ return insecure;
+ }
+
+ /**
+ * Return the mirror-by-digest-only setting.
+ * @return the mirror-by-digest-only setting
+ */
+ @Nullable
+ @JsonProperty("mirror-by-digest-only")
+ Boolean mirrorByDigestOnly() {
+ return mirrorByDigestOnly;
+ }
+
+ /**
+ * Return the list of mirror configurations.
+ * @return the list of mirror configurations
+ */
+ @Nullable
+ @JsonProperty("mirror")
+ List mirrors() {
+ return mirrors;
+ }
+
/**
* Return true if this registry is blocked and cannot be used for pulling or pushing images.
* @return true if blocked
@@ -211,15 +358,67 @@ public boolean isInsecure() {
public boolean isMirrorByDigestOnly() {
return mirrorByDigestOnly != null && mirrorByDigestOnly;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RegistryConfig)) return false;
+ RegistryConfig that = (RegistryConfig) o;
+ return Objects.equals(prefix, that.prefix)
+ && Objects.equals(location, that.location)
+ && Objects.equals(blocked, that.blocked)
+ && Objects.equals(insecure, that.insecure)
+ && Objects.equals(mirrorByDigestOnly, that.mirrorByDigestOnly)
+ && Objects.equals(mirrors, that.mirrors);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, location, blocked, insecure, mirrorByDigestOnly, mirrors);
+ }
+
+ @Override
+ public String toString() {
+ return "RegistryConfig[prefix=" + prefix + ", location=" + location + ", blocked=" + blocked
+ + ", insecure=" + insecure + ", mirrorByDigestOnly=" + mirrorByDigestOnly + ", mirrors=" + mirrors
+ + "]";
+ }
}
/**
* The model of the parsed prefix, which contains the host and path components of the prefix.
- * @param host The host component of the prefix, which can be a specific hostname or a wildcard pattern (e.g., *.example.com).
- * @param path The path component of the prefix, which can be a specific path or a path prefix (e.g., namespace/repo).
*/
@OrasModel
- record ParsedPrefix(String host, String path) {
+ static final class ParsedPrefix {
+
+ private final String host;
+ private final String path;
+
+ /**
+ * Constructor for ParsedPrefix.
+ * @param host The host component of the prefix, which can be a specific hostname or a wildcard pattern (e.g., *.example.com).
+ * @param path The path component of the prefix, which can be a specific path or a path prefix (e.g., namespace/repo).
+ */
+ ParsedPrefix(String host, String path) {
+ this.host = host;
+ this.path = path;
+ }
+
+ /**
+ * Return the host component of the prefix.
+ * @return the host
+ */
+ String host() {
+ return host;
+ }
+
+ /**
+ * Return the path component of the prefix.
+ * @return the path
+ */
+ String path() {
+ return path;
+ }
static ParsedPrefix parse(String prefix) {
int slash = prefix.indexOf('/');
@@ -228,6 +427,24 @@ static ParsedPrefix parse(String prefix) {
}
return new ParsedPrefix(prefix.substring(0, slash), prefix.substring(slash + 1));
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ParsedPrefix)) return false;
+ ParsedPrefix that = (ParsedPrefix) o;
+ return Objects.equals(host, that.host) && Objects.equals(path, that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(host, path);
+ }
+
+ @Override
+ public String toString() {
+ return "ParsedPrefix[host=" + host + ", path=" + path + "]";
+ }
}
/**
@@ -320,16 +537,96 @@ public String getKey() {
/**
* The model of the configuration file, which contains the list of registry configurations, aliases, and unqualified registries.
- * @param registries The list of registry configurations, each containing the registry location, whether it is blocked, and whether it is insecure.
- * @param aliases The map of registry aliases, where the key is the alias and the value is the actual registry URL.
- * @param unqualifiedRegistries The list of unqualified registries, which are registries that can be used without specifying a registry.
*/
@OrasModel
- record ConfigFile(
- @JsonProperty("short-name-mode") @Nullable ShortNameMode shortNameMode,
- @JsonProperty("registry") @Nullable List registries,
- @JsonProperty("aliases") @Nullable Map aliases,
- @JsonProperty("unqualified-search-registries") @Nullable List unqualifiedRegistries) {}
+ static final class ConfigFile {
+
+ private final @Nullable ShortNameMode shortNameMode;
+ private final @Nullable List registries;
+ private final @Nullable Map aliases;
+ private final @Nullable List unqualifiedRegistries;
+
+ /**
+ * Constructor for ConfigFile.
+ * @param shortNameMode The short name mode for the configuration.
+ * @param registries The list of registry configurations, each containing the registry location, whether it is blocked, and whether it is insecure.
+ * @param aliases The map of registry aliases, where the key is the alias and the value is the actual registry URL.
+ * @param unqualifiedRegistries The list of unqualified registries, which are registries that can be used without specifying a registry.
+ */
+ @JsonCreator
+ ConfigFile(
+ @JsonProperty("short-name-mode") @Nullable ShortNameMode shortNameMode,
+ @JsonProperty("registry") @Nullable List registries,
+ @JsonProperty("aliases") @Nullable Map aliases,
+ @JsonProperty("unqualified-search-registries") @Nullable List unqualifiedRegistries) {
+ this.shortNameMode = shortNameMode;
+ this.registries = registries;
+ this.aliases = aliases;
+ this.unqualifiedRegistries = unqualifiedRegistries;
+ }
+
+ /**
+ * Return the short name mode.
+ * @return the short name mode
+ */
+ @JsonProperty("short-name-mode")
+ @Nullable
+ ShortNameMode shortNameMode() {
+ return shortNameMode;
+ }
+
+ /**
+ * Return the list of registry configurations.
+ * @return the list of registry configurations
+ */
+ @JsonProperty("registry")
+ @Nullable
+ List registries() {
+ return registries;
+ }
+
+ /**
+ * Return the map of aliases.
+ * @return the map of aliases
+ */
+ @JsonProperty("aliases")
+ @Nullable
+ Map aliases() {
+ return aliases;
+ }
+
+ /**
+ * Return the list of unqualified registries.
+ * @return the list of unqualified registries
+ */
+ @JsonProperty("unqualified-search-registries")
+ @Nullable
+ List unqualifiedRegistries() {
+ return unqualifiedRegistries;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ConfigFile)) return false;
+ ConfigFile that = (ConfigFile) o;
+ return Objects.equals(shortNameMode, that.shortNameMode)
+ && Objects.equals(registries, that.registries)
+ && Objects.equals(aliases, that.aliases)
+ && Objects.equals(unqualifiedRegistries, that.unqualifiedRegistries);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(shortNameMode, registries, aliases, unqualifiedRegistries);
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigFile[shortNameMode=" + shortNameMode + ", registries=" + registries + ", aliases=" + aliases
+ + ", unqualifiedRegistries=" + unqualifiedRegistries + "]";
+ }
+ }
/**
* Get the list of unqualified registries.
@@ -510,11 +807,13 @@ public List getApplicableMirrors(ContainerRef ref) {
.filter(mirror -> {
PullFromMirror effective =
registryDigestOnly ? PullFromMirror.DIGEST_ONLY : mirror.effectivePullFromMirror();
- return switch (effective) {
- case DIGEST_ONLY -> refHasDigest;
- case TAG_ONLY -> refHasTag;
- case ALL -> true;
- };
+ if (effective == PullFromMirror.DIGEST_ONLY) {
+ return refHasDigest;
+ } else if (effective == PullFromMirror.TAG_ONLY) {
+ return refHasTag;
+ } else {
+ return true;
+ }
})
.collect(Collectors.toUnmodifiableList());
}
diff --git a/src/main/java/land/oras/auth/Scopes.java b/src/main/java/land/oras/auth/Scopes.java
index 051c08d9..f556ec59 100644
--- a/src/main/java/land/oras/auth/Scopes.java
+++ b/src/main/java/land/oras/auth/Scopes.java
@@ -23,6 +23,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
import land.oras.ContainerRef;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -147,7 +148,7 @@ public Scopes withAddedGlobalScopes(String... globalScopes) {
containerRef,
service,
identity,
- newScopes.stream().sorted().distinct().toList());
+ newScopes.stream().sorted().distinct().collect(Collectors.toList()));
}
/**
@@ -159,7 +160,7 @@ public Scopes withOnlyGlobalScopes() {
.filter(s -> !s.startsWith("repository:") && !s.startsWith("registry:"))
.sorted()
.distinct()
- .toList();
+ .collect(Collectors.toList());
return new Scopes(containerRef, service, identity, globalScopes);
}
@@ -172,7 +173,7 @@ public Scopes withoutGlobalScopes() {
.filter(s -> s.startsWith("repository:") || s.startsWith("registry:"))
.sorted()
.distinct()
- .toList();
+ .collect(Collectors.toList());
return new Scopes(containerRef, service, identity, nonGlobalScopes);
}
diff --git a/src/main/java/land/oras/exception/Error.java b/src/main/java/land/oras/exception/Error.java
index 8bdc46c1..09c02831 100644
--- a/src/main/java/land/oras/exception/Error.java
+++ b/src/main/java/land/oras/exception/Error.java
@@ -20,16 +20,84 @@
package land.oras.exception;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Objects;
import land.oras.OrasModel;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
* An error object for OCI API
- * @param code The error code
- * @param message The error message
- * @param details The error details
*/
@NullMarked
@OrasModel
-public record Error(String code, String message, @Nullable String details) {}
+public final class Error {
+
+ private final String code;
+ private final String message;
+ private final @Nullable String details;
+
+ /**
+ * Create a new error instance
+ * @param code The error code
+ * @param message The error message
+ * @param details The error details
+ */
+ @JsonCreator
+ public Error(
+ @JsonProperty("code") String code,
+ @JsonProperty("message") String message,
+ @JsonProperty("details") @Nullable String details) {
+ this.code = code;
+ this.message = message;
+ this.details = details;
+ }
+
+ /**
+ * Get the error code
+ * @return The error code
+ */
+ @JsonProperty("code")
+ public String code() {
+ return code;
+ }
+
+ /**
+ * Get the error message
+ * @return The error message
+ */
+ @JsonProperty("message")
+ public String message() {
+ return message;
+ }
+
+ /**
+ * Get the error details
+ * @return The error details
+ */
+ @JsonProperty("details")
+ public @Nullable String details() {
+ return details;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Error)) return false;
+ Error that = (Error) o;
+ return Objects.equals(code, that.code)
+ && Objects.equals(message, that.message)
+ && Objects.equals(details, that.details);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(code, message, details);
+ }
+
+ @Override
+ public String toString() {
+ return "Error[code=" + code + ", message=" + message + ", details=" + details + "]";
+ }
+}
diff --git a/src/main/java/land/oras/policy/ContainersPolicy.java b/src/main/java/land/oras/policy/ContainersPolicy.java
index 967ff1cf..f03b3eed 100644
--- a/src/main/java/land/oras/policy/ContainersPolicy.java
+++ b/src/main/java/land/oras/policy/ContainersPolicy.java
@@ -22,12 +22,15 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import land.oras.OrasModel;
import land.oras.exception.OrasException;
import land.oras.utils.JsonUtils;
@@ -35,8 +38,6 @@
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import tools.jackson.databind.ObjectMapper;
-import tools.jackson.databind.json.JsonMapper;
/**
* Represents the containers trust policy loaded from a {@code policy.json} file.
@@ -62,7 +63,13 @@ public class ContainersPolicy {
* that would interfere with the {@code @JsonTypeInfo} resolution here, so we use a separate
* instance.
*/
- static final ObjectMapper POLICY_MAPPER = JsonMapper.builder().build();
+ static final ObjectMapper POLICY_MAPPER;
+
+ static {
+ POLICY_MAPPER = JsonMapper.builder().build();
+ POLICY_MAPPER.configure(
+ com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
private final PolicyFile policyFile;
@@ -152,8 +159,9 @@ public void verify(PolicyContext context) {
List requirements = resolveRequirements(context.getTransport(), context.getScope());
for (PolicyRequirement req : requirements) {
if (!req.verify(context)) {
- throw new OrasException("Image '%s' rejected by containers policy requirement '%s'"
- .formatted(context.getReference(), req.getType()));
+ throw new OrasException(String.format(
+ "Image '%s' rejected by containers policy requirement '%s'",
+ context.getReference(), req.getType()));
}
}
LOG.debug("Policy verification passed for {}", context.getReference());
@@ -279,15 +287,45 @@ private static List defaultPolicyPaths() {
/**
* The raw JSON model for a {@code policy.json} file.
- *
- * @param defaultRequirements the mandatory global default requirement list (key
- * {@code "default"} in JSON).
- * @param transports optional per-transport requirement map.
*/
@OrasModel
- record PolicyFile(
- List defaultRequirements,
- Map>> transports) {
+ static final class PolicyFile {
+
+ private final List defaultRequirements;
+ private final Map>> transports;
+
+ /**
+ * Construct a new PolicyFile.
+ *
+ * @param defaultRequirements the mandatory global default requirement list.
+ * @param transports the per-transport requirement map.
+ */
+ PolicyFile(
+ List defaultRequirements,
+ Map>> transports) {
+ this.defaultRequirements = defaultRequirements;
+ this.transports = transports;
+ }
+
+ /**
+ * Return the default requirements.
+ *
+ * @return the default requirements.
+ */
+ @JsonProperty("default")
+ List defaultRequirements() {
+ return defaultRequirements;
+ }
+
+ /**
+ * Return the transports.
+ *
+ * @return the transports.
+ */
+ @JsonProperty("transports")
+ Map>> transports() {
+ return transports;
+ }
/**
* Deserialize a {@link PolicyFile} from its JSON form, mapping the raw transport keys to the
@@ -311,5 +349,24 @@ static PolicyFile fromJson(
}
return new PolicyFile(defaults, byTransport);
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PolicyFile)) return false;
+ PolicyFile that = (PolicyFile) o;
+ return Objects.equals(defaultRequirements, that.defaultRequirements)
+ && Objects.equals(transports, that.transports);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(defaultRequirements, transports);
+ }
+
+ @Override
+ public String toString() {
+ return "PolicyFile[defaultRequirements=" + defaultRequirements + ", transports=" + transports + "]";
+ }
}
}
diff --git a/src/main/java/land/oras/policy/PolicyRequirement.java b/src/main/java/land/oras/policy/PolicyRequirement.java
index 456a3cdf..345cc5e8 100644
--- a/src/main/java/land/oras/policy/PolicyRequirement.java
+++ b/src/main/java/land/oras/policy/PolicyRequirement.java
@@ -46,11 +46,7 @@
@JsonSubTypes.Type(value = PolicyRequirement.SigstoreSigned.class, name = PolicyRequirement.SigstoreSigned.TYPE),
})
@OrasModel
-public abstract sealed class PolicyRequirement
- permits PolicyRequirement.InsecureAcceptAnything,
- PolicyRequirement.Reject,
- PolicyRequirement.SignedBy,
- PolicyRequirement.SigstoreSigned {
+public abstract class PolicyRequirement {
/**
* Private constructor
diff --git a/src/main/java/land/oras/policy/SigstoreVerifier.java b/src/main/java/land/oras/policy/SigstoreVerifier.java
index a38b6017..c51726fb 100644
--- a/src/main/java/land/oras/policy/SigstoreVerifier.java
+++ b/src/main/java/land/oras/policy/SigstoreVerifier.java
@@ -20,7 +20,10 @@
package land.oras.policy;
+import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -33,6 +36,7 @@
import java.util.Base64;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import land.oras.OrasModel;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
@@ -40,8 +44,6 @@
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import tools.jackson.databind.ObjectMapper;
-import tools.jackson.databind.json.JsonMapper;
/**
* Verifies keyed Sigstore bundles attached to
@@ -79,7 +81,12 @@ final class SigstoreVerifier {
/**
* The JSON mapper for parsing Sigstore bundles and in-toto statements.
*/
- private static final ObjectMapper MAPPER = JsonMapper.builder().build();
+ private static final ObjectMapper MAPPER;
+
+ static {
+ MAPPER = JsonMapper.builder().build();
+ MAPPER.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ }
/**
* DSSE prefix as byte
@@ -187,7 +194,13 @@ static boolean verifyBundle(byte[] bundleBytes, String imageDigest, PublicKey tr
* digest in one of its subjects.
*/
private static boolean digestMatches(byte[] payload, String imageDigest) {
- InTotoStatement statement = MAPPER.readValue(payload, InTotoStatement.class);
+ InTotoStatement statement;
+ try {
+ statement = MAPPER.readValue(payload, InTotoStatement.class);
+ } catch (IOException e) {
+ LOG.warn("Failed to parse in-toto statement: {}", e.getMessage());
+ return false;
+ }
if (statement.subject() == null) {
LOG.warn("Sigstore bundle in-toto statement does not contain any subjects");
return false;
@@ -213,11 +226,12 @@ private static boolean digestMatches(byte[] payload, String imageDigest) {
*/
private static boolean verifySignature(PublicKey key, byte[] content, byte[] signature) {
// Might support other algorithm in the future
- String algorithm =
- switch (key.getAlgorithm()) {
- case Const.KEY_EC_ALGORITHM -> Const.KEY_SHA256_ECDSA_SIGNATURE_ALGORITHM;
- default -> null;
- };
+ String algorithm;
+ if (Const.KEY_EC_ALGORITHM.equals(key.getAlgorithm())) {
+ algorithm = Const.KEY_SHA256_ECDSA_SIGNATURE_ALGORITHM;
+ } else {
+ algorithm = null;
+ }
if (algorithm == null) {
LOG.warn("Unsupported public key algorithm for Sigstore verification: {}", key.getAlgorithm());
return false;
@@ -365,38 +379,273 @@ static List loadKeys(PolicyRequirement.SigstoreSigned requirement) {
* Only the DSSE envelope is modeled; verification material (tlog/timestamps) is ignored.
*/
@OrasModel
- record Bundle(@JsonProperty("dsseEnvelope") @Nullable DsseEnvelope dsseEnvelope) {}
+ static final class Bundle {
+
+ private final @Nullable DsseEnvelope dsseEnvelope;
+
+ /**
+ * Construct a new Bundle.
+ *
+ * @param dsseEnvelope the DSSE envelope.
+ */
+ @JsonCreator
+ Bundle(@JsonProperty("dsseEnvelope") @Nullable DsseEnvelope dsseEnvelope) {
+ this.dsseEnvelope = dsseEnvelope;
+ }
+
+ /**
+ * Return the DSSE envelope.
+ *
+ * @return the DSSE envelope.
+ */
+ @JsonProperty("dsseEnvelope")
+ @Nullable
+ DsseEnvelope dsseEnvelope() {
+ return dsseEnvelope;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Bundle)) return false;
+ Bundle that = (Bundle) o;
+ return Objects.equals(dsseEnvelope, that.dsseEnvelope);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(dsseEnvelope);
+ }
+
+ @Override
+ public String toString() {
+ return "Bundle[dsseEnvelope=" + dsseEnvelope + "]";
+ }
+ }
/**
* DSSE envelope containing a base64-encoded payload and one or more signatures.
- * @param payload The payload
- * @param payloadType Type of payload
- * @param signatures List of signatures
*/
@OrasModel
- record DsseEnvelope(
- @JsonProperty("payload") @Nullable String payload,
- @JsonProperty("payloadType") @Nullable String payloadType,
- @JsonProperty("signatures") @Nullable List signatures) {}
+ static final class DsseEnvelope {
+
+ private final @Nullable String payload;
+ private final @Nullable String payloadType;
+ private final @Nullable List signatures;
+
+ /**
+ * Construct a new DsseEnvelope.
+ *
+ * @param payload The payload.
+ * @param payloadType Type of payload.
+ * @param signatures List of signatures.
+ */
+ @JsonCreator
+ DsseEnvelope(
+ @JsonProperty("payload") @Nullable String payload,
+ @JsonProperty("payloadType") @Nullable String payloadType,
+ @JsonProperty("signatures") @Nullable List signatures) {
+ this.payload = payload;
+ this.payloadType = payloadType;
+ this.signatures = signatures;
+ }
+
+ /**
+ * Return the payload.
+ *
+ * @return the payload.
+ */
+ @JsonProperty("payload")
+ @Nullable
+ String payload() {
+ return payload;
+ }
+
+ /**
+ * Return the payload type.
+ *
+ * @return the payload type.
+ */
+ @JsonProperty("payloadType")
+ @Nullable
+ String payloadType() {
+ return payloadType;
+ }
+
+ /**
+ * Return the signatures.
+ *
+ * @return the signatures.
+ */
+ @JsonProperty("signatures")
+ @Nullable
+ List signatures() {
+ return signatures;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DsseEnvelope)) return false;
+ DsseEnvelope that = (DsseEnvelope) o;
+ return Objects.equals(payload, that.payload)
+ && Objects.equals(payloadType, that.payloadType)
+ && Objects.equals(signatures, that.signatures);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(payload, payloadType, signatures);
+ }
+
+ @Override
+ public String toString() {
+ return "DsseEnvelope[payload=" + payload + ", payloadType=" + payloadType + ", signatures=" + signatures
+ + "]";
+ }
+ }
/**
* A single DSSE signature (base64-encoded, DER for ECDSA)
- * @param sig Signature
*/
@OrasModel
- record DsseSignature(@JsonProperty("sig") @Nullable String sig) {}
+ static final class DsseSignature {
+
+ private final @Nullable String sig;
+
+ /**
+ * Construct a new DsseSignature.
+ *
+ * @param sig Signature.
+ */
+ @JsonCreator
+ DsseSignature(@JsonProperty("sig") @Nullable String sig) {
+ this.sig = sig;
+ }
+
+ /**
+ * Return the signature.
+ *
+ * @return the signature.
+ */
+ @JsonProperty("sig")
+ @Nullable
+ String sig() {
+ return sig;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DsseSignature)) return false;
+ DsseSignature that = (DsseSignature) o;
+ return Objects.equals(sig, that.sig);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(sig);
+ }
+
+ @Override
+ public String toString() {
+ return "DsseSignature[sig=" + sig + "]";
+ }
+ }
/**
* Minimal model of an in-toto statement, capturing only its subjects
- * @param subject The subject
*/
@OrasModel
- record InTotoStatement(@JsonProperty("subject") @Nullable List subject) {}
+ static final class InTotoStatement {
+
+ private final @Nullable List subject;
+
+ /**
+ * Construct a new InTotoStatement.
+ *
+ * @param subject The subject.
+ */
+ @JsonCreator
+ InTotoStatement(@JsonProperty("subject") @Nullable List subject) {
+ this.subject = subject;
+ }
+
+ /**
+ * Return the subject.
+ *
+ * @return the subject.
+ */
+ @JsonProperty("subject")
+ @Nullable
+ List subject() {
+ return subject;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof InTotoStatement)) return false;
+ InTotoStatement that = (InTotoStatement) o;
+ return Objects.equals(subject, that.subject);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(subject);
+ }
+
+ @Override
+ public String toString() {
+ return "InTotoStatement[subject=" + subject + "]";
+ }
+ }
/**
* An in-toto subject: a map of digest algorithm to hex value
- * @param digest The digest
*/
@OrasModel
- record Subject(@JsonProperty("digest") @Nullable Map digest) {}
+ static final class Subject {
+
+ private final @Nullable Map digest;
+
+ /**
+ * Construct a new Subject.
+ *
+ * @param digest The digest.
+ */
+ @JsonCreator
+ Subject(@JsonProperty("digest") @Nullable Map digest) {
+ this.digest = digest;
+ }
+
+ /**
+ * Return the digest.
+ *
+ * @return the digest.
+ */
+ @JsonProperty("digest")
+ @Nullable
+ Map digest() {
+ return digest;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Subject)) return false;
+ Subject that = (Subject) o;
+ return Objects.equals(digest, that.digest);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(digest);
+ }
+
+ @Override
+ public String toString() {
+ return "Subject[digest=" + digest + "]";
+ }
+ }
}
diff --git a/src/main/java/land/oras/utils/Const.java b/src/main/java/land/oras/utils/Const.java
index e2befe8e..369c02c2 100644
--- a/src/main/java/land/oras/utils/Const.java
+++ b/src/main/java/land/oras/utils/Const.java
@@ -217,13 +217,13 @@ private Const() {
/**
* The default accept type for the manifest
*/
- public static final String MANIFEST_ACCEPT_TYPE = "%s, %s, %s, %s, %s"
- .formatted(
- DEFAULT_INDEX_MEDIA_TYPE,
- DEFAULT_MANIFEST_MEDIA_TYPE,
- ARTIFACT_MANIFEST_MEDIA_TYPE,
- DOCKER_INDEX_MEDIA_TYPE,
- DOCKER_MANIFEST_MEDIA_TYPE);
+ public static final String MANIFEST_ACCEPT_TYPE = String.format(
+ "%s, %s, %s, %s, %s",
+ DEFAULT_INDEX_MEDIA_TYPE,
+ DEFAULT_MANIFEST_MEDIA_TYPE,
+ ARTIFACT_MANIFEST_MEDIA_TYPE,
+ DOCKER_INDEX_MEDIA_TYPE,
+ DOCKER_MANIFEST_MEDIA_TYPE);
/**
* Annotation for the title
diff --git a/src/main/java/land/oras/utils/DigestUtils.java b/src/main/java/land/oras/utils/DigestUtils.java
index f24d96a6..6e541aa9 100644
--- a/src/main/java/land/oras/utils/DigestUtils.java
+++ b/src/main/java/land/oras/utils/DigestUtils.java
@@ -27,7 +27,6 @@
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.Security;
-import java.util.HexFormat;
import land.oras.exception.OrasException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jspecify.annotations.NullMarked;
@@ -38,8 +37,6 @@
@NullMarked
final class DigestUtils {
- private static final HexFormat HEX_FORMAT = HexFormat.of();
-
static {
Security.addProvider(new BouncyCastleProvider());
}
@@ -118,8 +115,16 @@ static String digest(String algorithm, String prefix, InputStream input) {
}
}
+ static String toHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ sb.append(Character.forDigit((b >> 4) & 0xF, 16));
+ sb.append(Character.forDigit(b & 0xF, 16));
+ }
+ return sb.toString();
+ }
+
private static String formatHex(String prefix, final byte[] hashBytes) {
- String formatHex = HEX_FORMAT.formatHex(hashBytes);
- return prefix + ":" + formatHex;
+ return prefix + ":" + toHexString(hashBytes);
}
}
diff --git a/src/main/java/land/oras/utils/JsonUtils.java b/src/main/java/land/oras/utils/JsonUtils.java
index df626aa2..7274a20e 100644
--- a/src/main/java/land/oras/utils/JsonUtils.java
+++ b/src/main/java/land/oras/utils/JsonUtils.java
@@ -21,6 +21,10 @@
package land.oras.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@@ -30,13 +34,10 @@
import java.nio.file.Path;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;
-import tools.jackson.core.JacksonException;
-import tools.jackson.databind.ObjectMapper;
-import tools.jackson.databind.json.JsonMapper;
/**
* Utility class for JSON operations.
- * Use Jackson 3 internally for JSON operations
+ * Use Jackson internally for JSON operations
*/
@NullMarked
public final class JsonUtils {
@@ -54,9 +55,11 @@ private JsonUtils() {
}
static {
- jsonMapper = JsonMapper.builder()
- .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY))
- .build();
+ jsonMapper = JsonMapper.builder().build();
+ jsonMapper.setDefaultPropertyInclusion(
+ JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, JsonInclude.Include.USE_DEFAULTS));
+ jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ jsonMapper.registerModule(new JavaTimeModule());
}
/**
@@ -67,7 +70,7 @@ private JsonUtils() {
public static String toJson(Object object) {
try {
return jsonMapper.writeValueAsString(object);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to convert object to JSON string", e);
}
}
@@ -82,7 +85,7 @@ public static String toJson(Object object) {
public static T fromJson(String json, Class clazz) {
try {
return jsonMapper.readValue(json, clazz);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to parse JSON string", e);
}
}
@@ -97,7 +100,7 @@ public static T fromJson(String json, Class clazz) {
public static T fromJson(InputStream is, Class clazz) {
try {
return jsonMapper.readValue(is, clazz);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to parse JSON string", e);
}
}
@@ -115,8 +118,6 @@ public static T fromJson(Path path, Class clazz) {
try {
return jsonMapper.readValue(Files.readString(path, StandardCharsets.UTF_8), clazz);
} catch (IOException e) {
- throw new OrasException("Unable to read JSON file due to IO error", e);
- } catch (JacksonException e) {
throw new OrasException("Unable to parse JSON file", e);
}
}
@@ -136,8 +137,6 @@ public static T fromJson(Path path, Type type) {
Files.readString(path, StandardCharsets.UTF_8),
jsonMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
- throw new OrasException("Unable to read JSON file due to IO error", e);
- } catch (JacksonException e) {
throw new OrasException("Unable to parse JSON file", e);
}
}
@@ -154,7 +153,7 @@ public static T fromJson(Path path, Type type) {
public static T fromJson(Reader reader, Type type) {
try {
return jsonMapper.readValue(reader, jsonMapper.getTypeFactory().constructType(type));
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to parse JSON content", e);
}
}
diff --git a/src/main/java/land/oras/utils/SupportedAlgorithm.java b/src/main/java/land/oras/utils/SupportedAlgorithm.java
index 9a53a834..043387df 100644
--- a/src/main/java/land/oras/utils/SupportedAlgorithm.java
+++ b/src/main/java/land/oras/utils/SupportedAlgorithm.java
@@ -168,8 +168,9 @@ public static boolean isSupported(String digest) {
// Check the size
String value = digest.substring(algorithm.getPrefix().length() + 1);
if (value.length() != algorithm.getSize() * 2) {
- throw new OrasException("Invalid digest %s, expected size is %d, but got %d"
- .formatted(digest, algorithm.getSize(), value.length()));
+ throw new OrasException(String.format(
+ "Invalid digest %s, expected size is %d, but got %d",
+ digest, algorithm.getSize(), value.length()));
}
return true;
}
diff --git a/src/main/java/land/oras/utils/TomlUtils.java b/src/main/java/land/oras/utils/TomlUtils.java
index 642428cc..ab3d97e8 100644
--- a/src/main/java/land/oras/utils/TomlUtils.java
+++ b/src/main/java/land/oras/utils/TomlUtils.java
@@ -20,19 +20,18 @@
package land.oras.utils;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.toml.TomlMapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;
-import tools.jackson.core.JacksonException;
-import tools.jackson.databind.ObjectMapper;
-import tools.jackson.dataformat.toml.TomlMapper;
/**
* Utility class for TOML operations.
- * Use Jackson 3 internally for TOML operations
+ * Use Jackson internally for TOML operations
*/
@NullMarked
public final class TomlUtils {
@@ -61,7 +60,7 @@ private TomlUtils() {
public static String toToml(Object object) {
try {
return tomlMapper.writeValueAsString(object);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to convert object to TOML string", e);
}
}
@@ -76,7 +75,7 @@ public static String toToml(Object object) {
public static T fromToml(String toml, Class clazz) {
try {
return tomlMapper.readValue(toml, clazz);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to parse TOML string", e);
}
}
@@ -92,7 +91,7 @@ public static T fromToml(String toml, Class clazz) {
public static T fromToml(Path path, Class clazz) {
try {
return tomlMapper.readValue(Files.readString(path, StandardCharsets.UTF_8), clazz);
- } catch (IOException | JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to read TOML from file", e);
}
}
diff --git a/src/main/java/land/oras/utils/YamlUtils.java b/src/main/java/land/oras/utils/YamlUtils.java
index 20aa7075..478243ef 100644
--- a/src/main/java/land/oras/utils/YamlUtils.java
+++ b/src/main/java/land/oras/utils/YamlUtils.java
@@ -20,19 +20,18 @@
package land.oras.utils;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import land.oras.exception.OrasException;
import org.jspecify.annotations.NullMarked;
-import tools.jackson.core.JacksonException;
-import tools.jackson.databind.ObjectMapper;
-import tools.jackson.dataformat.yaml.YAMLMapper;
/**
* Utility class for YAML operations.
- * Use Jackson 3 internally for YAML operations
+ * Use Jackson internally for YAML operations
*/
@NullMarked
public final class YamlUtils {
@@ -61,7 +60,7 @@ private YamlUtils() {
public static String toYaml(Object object) {
try {
return yamlMapper.writeValueAsString(object);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to convert object to YAML string", e);
}
}
@@ -76,7 +75,7 @@ public static String toYaml(Object object) {
public static T fromYaml(String yaml, Class clazz) {
try {
return yamlMapper.readValue(yaml, clazz);
- } catch (JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to parse YAML string", e);
}
}
@@ -92,7 +91,7 @@ public static T fromYaml(String yaml, Class clazz) {
public static T fromYaml(Path path, Class clazz) {
try {
return yamlMapper.readValue(Files.readString(path, StandardCharsets.UTF_8), clazz);
- } catch (IOException | JacksonException e) {
+ } catch (IOException e) {
throw new OrasException("Unable to read YAML from file", e);
}
}
diff --git a/src/test/java/land/oras/AnnotationsTest.java b/src/test/java/land/oras/AnnotationsTest.java
index ed1982c0..a9c7adbd 100644
--- a/src/test/java/land/oras/AnnotationsTest.java
+++ b/src/test/java/land/oras/AnnotationsTest.java
@@ -100,18 +100,16 @@ void testToString() {
}
private String sampleAnnotations() {
- return """
- {
- "$config": {
- "hello": "world"
- },
- "$manifest": {
- "foo": "bar"
- },
- "cake.txt": {
- "fun": "more cream"
- }
- }
- """;
+ return " {\n"
+ + " \"$config\": {\n"
+ + " \"hello\": \"world\"\n"
+ + " },\n"
+ + " \"$manifest\": {\n"
+ + " \"foo\": \"bar\"\n"
+ + " },\n"
+ + " \"cake.txt\": {\n"
+ + " \"fun\": \"more cream\"\n"
+ + " }\n"
+ + " }\n";
}
}
diff --git a/src/test/java/land/oras/ContainerRefTest.java b/src/test/java/land/oras/ContainerRefTest.java
index 559324f4..41e0942a 100644
--- a/src/test/java/land/oras/ContainerRefTest.java
+++ b/src/test/java/land/oras/ContainerRefTest.java
@@ -40,20 +40,17 @@ class ContainerRefTest {
@Execution(ExecutionMode.SAME_THREAD)
void shouldReadRegistriesConfig(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- location = "public.ecr.aws"
- blocked = true
-
- [[registry]]
- location = "docker.io/library/alpine"
- blocked = true
-
- [[registry]]
- location = "localhost:5000"
- insecure = true
- """;
+ String config = "[[registry]]\n"
+ + "location = \"public.ecr.aws\"\n"
+ + "blocked = true\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "location = \"docker.io/library/alpine\"\n"
+ + "blocked = true\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "location = \"localhost:5000\"\n"
+ + "insecure = true\n";
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -82,12 +79,9 @@ void shouldReadRegistriesConfig(@TempDir Path homeDir) throws Exception {
void shouldDetermineFromAlias(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [aliases]
- "my-library/my-namespace"="localhost:5000/test"
- "my-library"="localhost:5000/test2"
- """;
+ String config = "[aliases]\n"
+ + "\"my-library/my-namespace\"=\"localhost:5000/test\"\n"
+ + "\"my-library\"=\"localhost:5000/test2\"\n";
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -105,16 +99,13 @@ void shouldDetermineFromAlias(@TempDir Path homeDir) throws Exception {
void shouldRewriteAllSubdomainToLocalProxy(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- prefix = "*.example.com"
- location = "localhost:5000/example-com"
-
- [[registry]]
- prefix = "*.otherexample.io/library"
- location = "localhost:5001/docker"
- """;
+ String config = "[[registry]]\n"
+ + "prefix = \"*.example.com\"\n"
+ + "location = \"localhost:5000/example-com\"\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "prefix = \"*.otherexample.io/library\"\n"
+ + "location = \"localhost:5001/docker\"\n";
TestUtils.createRegistriesConfFile(homeDir, config);
diff --git a/src/test/java/land/oras/DockerIoITCase.java b/src/test/java/land/oras/DockerIoITCase.java
index 2251da8c..945c7e35 100644
--- a/src/test/java/land/oras/DockerIoITCase.java
+++ b/src/test/java/land/oras/DockerIoITCase.java
@@ -116,7 +116,7 @@ void shouldCopyTagToInternalRegistry() {
ContainerRef containerSource = ContainerRef.parse("docker.io/library/alpine:latest");
ContainerRef containerTarget =
- ContainerRef.parse("%s/docker/library/alpine:latest".formatted(unsecureRegistry.getRegistry()));
+ ContainerRef.parse(String.format("%s/docker/library/alpine:latest", unsecureRegistry.getRegistry()));
CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, CopyUtils.CopyOptions.deep());
assertTrue(targetRegistry.exists(containerTarget));
@@ -149,11 +149,7 @@ void shouldContainsCommonLinuxPlatform() {
void shouldCopyTagToInternalRegistryViaAlias(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [aliases]
- "dockerhub-alpine" = "docker.io/library/alpine"
- """;
+ String config = "[aliases]\n" + "\"dockerhub-alpine\" = \"docker.io/library/alpine\"\n";
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -167,8 +163,8 @@ void shouldCopyTagToInternalRegistryViaAlias(@TempDir Path homeDir) throws Excep
.build();
ContainerRef containerSource = ContainerRef.parse("dockerhub-alpine");
- ContainerRef containerTarget =
- ContainerRef.parse("%s/docker/library/alpine:latest".formatted(unsecureRegistry.getRegistry()));
+ ContainerRef containerTarget = ContainerRef.parse(
+ String.format("%s/docker/library/alpine:latest", unsecureRegistry.getRegistry()));
CopyUtils.copy(
sourceRegistry, containerSource, targetRegistry, containerTarget, CopyUtils.CopyOptions.deep());
diff --git a/src/test/java/land/oras/GitHubContainerRegistryITCase.java b/src/test/java/land/oras/GitHubContainerRegistryITCase.java
index da99aad3..516efc9c 100644
--- a/src/test/java/land/oras/GitHubContainerRegistryITCase.java
+++ b/src/test/java/land/oras/GitHubContainerRegistryITCase.java
@@ -85,27 +85,21 @@ void shouldPullSignedImage(@TempDir Path homeDir) throws Exception {
Path publicKeyPath = Path.of("src/test/resources/keys/sigstore/alpine-signed.pub");
// language=toml
- String config =
- """
- [[registry]]
- location = "ghcr.io"
- insecure = false
- """;
+ String config = "[[registry]]\n" + "location = \"ghcr.io\"\n" + "insecure = false\n";
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "ghcr.io/jonesbusy/alpine-signed": [{"type": "sigstoreSigned", "keyPath": "%s"}]
- }
- }
- }
- """
- .formatted(publicKeyPath.toAbsolutePath().toString()));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"ghcr.io/jonesbusy/alpine-signed\": [{\"type\": \"sigstoreSigned\", \"keyPath\": \"%s\"}]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ publicKeyPath.toAbsolutePath().toString()));
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -120,11 +114,7 @@ void shouldPullSignedImage(@TempDir Path homeDir) throws Exception {
@Execution(ExecutionMode.SAME_THREAD)
void shouldPullIndexWithAlias(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [aliases]
- "oras"="ghcr.io/oras-project/oras"
- """;
+ String config = "[aliases]\n" + "\"oras\"=\"ghcr.io/oras-project/oras\"\n";
// Setup
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -182,7 +172,7 @@ void shouldCopyTagToInternalRegistry() {
ContainerRef containerSource = ContainerRef.parse("ghcr.io/oras-project/oras:main");
ContainerRef containerTarget =
- ContainerRef.parse("%s/docker/library/oras:main".formatted(unsecureRegistry.getRegistry()));
+ ContainerRef.parse(String.format("%s/docker/library/oras:main", unsecureRegistry.getRegistry()));
CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, CopyUtils.CopyOptions.deep());
assertTrue(targetRegistry.exists(containerTarget));
diff --git a/src/test/java/land/oras/HarborS3ITCase.java b/src/test/java/land/oras/HarborS3ITCase.java
index a181e15f..f7dfc451 100644
--- a/src/test/java/land/oras/HarborS3ITCase.java
+++ b/src/test/java/land/oras/HarborS3ITCase.java
@@ -178,20 +178,17 @@ void shouldPushJenkinsLibArtifact() {
void shouldPushJenkinsScriptArtifact() {
// language=groovy
- String jenkinsfile =
- """
- node {
- stage('Build') {
- echo 'Building...'
- }
- stage('Test') {
- echo 'Testing...'
- }
- stage('Deploy') {
- echo 'Deploying...'
- }
- }
- """;
+ String jenkinsfile = "node {\n"
+ + " stage('Build') {\n"
+ + " echo 'Building...'\n"
+ + " }\n"
+ + " stage('Test') {\n"
+ + " echo 'Testing...'\n"
+ + " }\n"
+ + " stage('Deploy') {\n"
+ + " echo 'Deploying...'\n"
+ + " }\n"
+ + "}\n";
// The compressed manifests
Path image = Paths.get("src/test/resources/img").resolve("jenkins.png");
diff --git a/src/test/java/land/oras/JFrogArtifactoryITCase.java b/src/test/java/land/oras/JFrogArtifactoryITCase.java
index 9cf63f9e..8d53f068 100644
--- a/src/test/java/land/oras/JFrogArtifactoryITCase.java
+++ b/src/test/java/land/oras/JFrogArtifactoryITCase.java
@@ -74,7 +74,7 @@ void shouldCopyTagToInternalRegistry() {
ContainerRef containerSource = ContainerRef.parse("releases-docker.jfrog.io/jfrog/jfrog-cli-v2-jf");
ContainerRef containerTarget =
- ContainerRef.parse("%s/jfrog/jfrog-cli-v2-jf".formatted(unsecureRegistry.getRegistry()));
+ ContainerRef.parse(String.format("%s/jfrog/jfrog-cli-v2-jf", unsecureRegistry.getRegistry()));
CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, true);
assertTrue(targetRegistry.exists(containerTarget));
diff --git a/src/test/java/land/oras/LayerTest.java b/src/test/java/land/oras/LayerTest.java
index 76e33f83..25b2f510 100644
--- a/src/test/java/land/oras/LayerTest.java
+++ b/src/test/java/land/oras/LayerTest.java
@@ -77,14 +77,11 @@ void shouldHaveEmptyLayer() {
@Test
void shouldReadNullAnnotations() {
- String json =
- """
- {
- "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
- "digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "size": 32654
- }
- """;
+ String json = " {\n"
+ + " \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n"
+ + " \"digest\": \"sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n"
+ + " \"size\": 32654\n"
+ + " }\n";
Layer layer = Layer.fromJson(json);
assertEquals("application/vnd.oci.image.layer.v1.tar+gzip", layer.getMediaType());
assertEquals("sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", layer.getDigest());
@@ -94,14 +91,11 @@ void shouldReadNullAnnotations() {
@Test
void shouldReadBlobData() {
- String json =
- """
- {
- "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
- "digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "data": "e30="
- }
- """;
+ String json = " {\n"
+ + " \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n"
+ + " \"digest\": \"sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n"
+ + " \"data\": \"e30=\"\n"
+ + " }\n";
Layer layer = Layer.fromJson(json);
assertEquals("application/vnd.oci.image.layer.v1.tar+gzip", layer.getMediaType());
assertEquals("sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", layer.getDigest());
@@ -139,15 +133,13 @@ void testToString() {
}
private String emptyLayer() {
- return """
- {
- "mediaType": "application/vnd.oci.empty.v1+json",
- "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
- "size": 2,
- "data": "e30=",
- "annotations": {}
- }
- """;
+ return " {\n"
+ + " \"mediaType\": \"application/vnd.oci.empty.v1+json\",\n"
+ + " \"digest\": \"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\",\n"
+ + " \"size\": 2,\n"
+ + " \"data\": \"e30=\",\n"
+ + " \"annotations\": {}\n"
+ + " }\n";
}
/**
@@ -155,14 +147,11 @@ private String emptyLayer() {
* @return The manifest
*/
private String sampleLayer() {
- return Layer.fromJson(
- """
- {
- "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
- "digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "size": 32654
- }
- """)
+ return Layer.fromJson(" {\n"
+ + " \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n"
+ + " \"digest\": \"sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n"
+ + " \"size\": 32654\n"
+ + " }\n")
.toJson();
}
}
diff --git a/src/test/java/land/oras/LayoutRefTest.java b/src/test/java/land/oras/LayoutRefTest.java
index cb825767..163edc85 100644
--- a/src/test/java/land/oras/LayoutRefTest.java
+++ b/src/test/java/land/oras/LayoutRefTest.java
@@ -44,7 +44,7 @@ class LayoutRefTest {
@Test
void shouldParseLayoutWithAllParts() {
String ociLayout = tempDir.resolve("foo").toString();
- LayoutRef layoutRef = LayoutRef.parse("%s:v1".formatted(ociLayout));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:v1", ociLayout));
assertEquals("v1", layoutRef.getTag());
assertEquals(ociLayout, layoutRef.getFolder().toString());
assertEquals(ociLayout, layoutRef.getRepository());
@@ -55,7 +55,7 @@ void shouldParseLayoutWithAllParts() {
void shouldParseLayoutWithDigest() {
String ociLayout = tempDir.resolve("foo").toString();
LayoutRef layoutRef = LayoutRef.parse(
- "%s@sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824".formatted(ociLayout));
+ String.format("%s@sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", ociLayout));
assertEquals("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", layoutRef.getTag());
assertEquals(ociLayout, layoutRef.getFolder().toString());
assertEquals(ociLayout, layoutRef.getRepository());
diff --git a/src/test/java/land/oras/ManifestDescriptorTest.java b/src/test/java/land/oras/ManifestDescriptorTest.java
index 92d0b611..71bc3d5b 100644
--- a/src/test/java/land/oras/ManifestDescriptorTest.java
+++ b/src/test/java/land/oras/ManifestDescriptorTest.java
@@ -86,21 +86,19 @@ void testToString() {
private String descriptor() {
// language=json
- return """
- {
- "mediaType": "application/vnd.oci.image.manifest.v1+json",
- "digest": "sha256:09c8ec8bf0d43a250ba7fed2eb6f242935b2987be5ed921ee06c93008558f980",
- "size": 838,
- "annotations": {
- "com.docker.official-images.bashbrew.arch": "riscv64",
- "vnd.docker.reference.digest": "sha256:1de5eb4a9a6735adb46b2c9c88674c0cfba3444dd4ac2341b3babf1261700529",
- "vnd.docker.reference.type": "attestation-manifest"
- },
- "platform": {
- "architecture": "unknown",
- "os": "unknown"
- }
- }
- """;
+ return "{\n"
+ + " \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n"
+ + " \"digest\": \"sha256:09c8ec8bf0d43a250ba7fed2eb6f242935b2987be5ed921ee06c93008558f980\",\n"
+ + " \"size\": 838,\n"
+ + " \"annotations\": {\n"
+ + " \"com.docker.official-images.bashbrew.arch\": \"riscv64\",\n"
+ + " \"vnd.docker.reference.digest\": \"sha256:1de5eb4a9a6735adb46b2c9c88674c0cfba3444dd4ac2341b3babf1261700529\",\n"
+ + " \"vnd.docker.reference.type\": \"attestation-manifest\"\n"
+ + " },\n"
+ + " \"platform\": {\n"
+ + " \"architecture\": \"unknown\",\n"
+ + " \"os\": \"unknown\"\n"
+ + " }\n"
+ + "}\n";
}
}
diff --git a/src/test/java/land/oras/ManifestTest.java b/src/test/java/land/oras/ManifestTest.java
index 09d29651..f1888579 100644
--- a/src/test/java/land/oras/ManifestTest.java
+++ b/src/test/java/land/oras/ManifestTest.java
@@ -139,20 +139,18 @@ void shouldGetArtifactTest() {
}
private String emptyManifest() {
- return """
- {
- "schemaVersion": 2,
- "mediaType": "application/vnd.oci.image.manifest.v1+json",
- "config": {
- "mediaType": "application/vnd.oci.empty.v1+json",
- "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
- "size": 2,
- "data": "e30="
- },
- "layers": [],
- "annotations": {}
- }
- """;
+ return "{\n"
+ + " \"schemaVersion\": 2,\n"
+ + " \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n"
+ + " \"config\": {\n"
+ + " \"mediaType\": \"application/vnd.oci.empty.v1+json\",\n"
+ + " \"digest\": \"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\",\n"
+ + " \"size\": 2,\n"
+ + " \"data\": \"e30=\"\n"
+ + " },\n"
+ + " \"layers\": [],\n"
+ + " \"annotations\": {}\n"
+ + "}\n";
}
/**
@@ -161,28 +159,26 @@ private String emptyManifest() {
*/
private String sampleManifest() {
// language=JSON
- return """
- {
- "schemaVersion": 2,
- "mediaType": "application/vnd.oci.image.manifest.v1+json",
- "config": {
- "mediaType": "application/vnd.oci.image.config.v1+json",
- "digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "size": 7023
- },
- "layers": [
- {
- "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
- "digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "size": 32654
- },
- {
- "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
- "digest": "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- "size": 1048576
- }
- ]
- }
- """;
+ return " {\n"
+ + " \"schemaVersion\": 2,\n"
+ + " \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n"
+ + " \"config\": {\n"
+ + " \"mediaType\": \"application/vnd.oci.image.config.v1+json\",\n"
+ + " \"digest\": \"sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n"
+ + " \"size\": 7023\n"
+ + " },\n"
+ + " \"layers\": [\n"
+ + " {\n"
+ + " \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n"
+ + " \"digest\": \"sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n"
+ + " \"size\": 32654\n"
+ + " },\n"
+ + " {\n"
+ + " \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n"
+ + " \"digest\": \"sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\n"
+ + " \"size\": 1048576\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n";
}
}
diff --git a/src/test/java/land/oras/OCILayoutTest.java b/src/test/java/land/oras/OCILayoutTest.java
index 849b0673..b5af69a9 100644
--- a/src/test/java/land/oras/OCILayoutTest.java
+++ b/src/test/java/land/oras/OCILayoutTest.java
@@ -65,7 +65,7 @@ class OCILayoutTest {
@Test
void shouldPushEmptyManifest() {
Path path = layoutPath.resolve("shouldPushManifest");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Manifest manifest = Manifest.empty().withConfig(Config.empty());
manifest = ociLayout.pushManifest(layoutRef, manifest);
@@ -110,7 +110,7 @@ void shouldPushEmptyManifest() {
@Test
void shouldPushIndex() {
Path path = layoutPath.resolve("shouldPushIndex");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Index index = Index.fromManifests(List.of(Manifest.empty().getDescriptor()));
index = ociLayout.pushIndex(layoutRef, index);
@@ -148,7 +148,7 @@ void shouldPushSignedLayoutAndPullItAndValidateSignature(@TempDir Path homeDir)
// A signed alpine OCI layout
String imageDigest = "sha256:9e56ed4cb843f61658fcdb17d4205a87d5e217515f23831314b2173a776174d6";
- LayoutRef layoutRef = LayoutRef.parse("src/test/resources/oci/alpine-signed@%s".formatted(imageDigest));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("src/test/resources/oci/alpine-signed@%s", imageDigest));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(layoutRef.getFolder()).build();
@@ -158,7 +158,7 @@ void shouldPushSignedLayoutAndPullItAndValidateSignature(@TempDir Path homeDir)
.withInsecure(true)
.build();
ContainerRef targetRef =
- ContainerRef.parse("%s/library/alpine-signed:latest".formatted(registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/alpine-signed:latest", registry.getRegistry()));
CopyUtils.copy(ociLayout, layoutRef, pushRegistry, targetRef, CopyUtils.CopyOptions.deep());
// Trust policy: accept only images carrying a valid Sigstore signature made by this key.
@@ -168,17 +168,16 @@ void shouldPushSignedLayoutAndPullItAndValidateSignature(@TempDir Path homeDir)
// language=json
Files.writeString(
policyPath,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "%s/library/alpine-signed": [{"type": "sigstoreSigned", "keyPath": "%s"}]
- }
- }
- }
- """
- .formatted(registry.getRegistry(), publicKeyPath.toAbsolutePath()));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"%s/library/alpine-signed\": [{\"type\": \"sigstoreSigned\", \"keyPath\": \"%s\"}]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ registry.getRegistry(), publicKeyPath.toAbsolutePath()));
ContainersPolicy policy = ContainersPolicy.newPolicy(policyPath);
// Pull the manifest with the policy: the attached signature is fetched and verified.
@@ -235,7 +234,7 @@ void shouldListRepositories() throws Exception {
@Test
void shouldPushConfig() throws IOException {
Path path = layoutPath.resolve("shouldPushConfig");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Config config = Config.empty();
ociLayout.pushConfig(layoutRef.withDigest(config.getDigest()), config);
@@ -253,7 +252,7 @@ void shouldPushConfig() throws IOException {
@Test
void shouldPushConfigWithReference() throws IOException {
Path path = layoutPath.resolve("shouldPushConfigWithReference");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Path configFile = blobDir.resolve("config.txt");
Files.writeString(configFile, "hello");
@@ -275,7 +274,7 @@ void shouldPushConfigWithReference() throws IOException {
@Test
void shouldRejectTamperedBlobOnRead() throws IOException {
Path path = layoutPath.resolve("tamperedBlob");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
// Push a known blob into the layout
@@ -308,7 +307,7 @@ void shouldRejectTamperedBlobOnRead() throws IOException {
@Test
void shouldReadUntamperedBlobAfterIntegrityCheck() throws IOException {
Path path = layoutPath.resolve("untamperedBlob");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Path blobFile = blobDir.resolve("intact.txt");
@@ -333,7 +332,7 @@ void shouldRejectTamperedLayerOnPullArtifact() throws IOException {
Path artifactPath = blobDir.resolve("artifact.txt");
Files.writeString(artifactPath, "artifact-content");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
Annotations annotations = Annotations.ofManifest(Map.of(Const.ANNOTATION_CREATED, Const.currentTimestamp()));
@@ -400,7 +399,7 @@ void verifyBlobDigestIgnoresMissingBlob() {
@Test
void shouldPushIndexWithTag() {
Path path = layoutPath.resolve("shouldPushIndexWithTag");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Index index = Index.fromManifests(List.of(Manifest.empty().getDescriptor()));
index = ociLayout.pushIndex(layoutRef.withTag("latest"), index);
@@ -423,7 +422,7 @@ void shouldPushIndexWithTag() {
void shouldPushManifestFromFile() {
Path path = layoutPath.resolve("shouldPushManifetFromFile");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Manifest manifest = Manifest.fromPath(
@@ -461,7 +460,7 @@ void shouldPushManifestFromFile() {
@Test
void shouldPushEmptyManifestWithRef() {
Path path = layoutPath.resolve("shouldPushManifest");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", path.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(path).build();
Manifest manifest = Manifest.empty().withConfig(Config.empty());
manifest = ociLayout.pushManifest(layoutRef, manifest);
@@ -531,7 +530,7 @@ void failToCreateLayoutIfFileExists() throws IOException {
Path path = layoutPath.resolve("failToCreateLayoutIfFileExists");
Files.createFile(path);
assertThrows(OrasException.class, () -> {
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(path.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", path.toString()));
OCILayout.Builder.builder().defaults(layoutRef.getFolder()).build();
});
}
@@ -543,7 +542,7 @@ void shouldPushToOciLayoutWithoutTag() throws IOException {
Path artifactPath = blobDir.resolve("shouldPushToOciLayoutWithoutTag.txt");
Files.writeString(artifactPath, "hi");
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
@@ -640,7 +639,7 @@ void shouldPushToOciLayoutWithTag() throws IOException {
Path artifactPath = blobDir.resolve("shouldPushToOciLayoutWithTag.txt");
Files.writeString(artifactPath, "hi");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
@@ -916,7 +915,7 @@ void cannotPushBlobFromPathWithoutTagOrDigest() throws IOException {
void cannotPushWithInvalidDigest() {
Path invalidBlobPushDir = layoutPath.resolve("cannotPushWithInvalidDigest");
- LayoutRef wrongDigest1 = LayoutRef.parse("%s@sha234:1234".formatted(invalidBlobPushDir.toString()));
+ LayoutRef wrongDigest1 = LayoutRef.parse(String.format("%s@sha234:1234", invalidBlobPushDir.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(invalidBlobPushDir).build();
@@ -925,7 +924,7 @@ void cannotPushWithInvalidDigest() {
ociLayout.pushBlob(wrongDigest1, "hi".getBytes(StandardCharsets.UTF_8));
});
- LayoutRef wrongDigest2 = LayoutRef.parse("%s@sha256:1234".formatted(invalidBlobPushDir.toString()));
+ LayoutRef wrongDigest2 = LayoutRef.parse(String.format("%s@sha256:1234", invalidBlobPushDir.toString()));
// Push more blobs
assertThrows(OrasException.class, () -> {
@@ -945,7 +944,7 @@ void testShouldCopyArtifactFromRegistryIntoOciLayout() throws IOException {
LayoutRef layoutRef = LayoutRef.of(ociLayout);
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-oci-layout".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-oci-layout", this.registry.getRegistry()));
Path file1 = blobDir.resolve("artifact-oci-layout.txt");
Path file2 = blobDir.resolve("artifact-recursive-oci-attached.txt");
Files.writeString(file1, "artifact-oci-layout");
@@ -985,10 +984,10 @@ void testShouldCopyIndexWithPlatformFilterFromRegistryIntoOciLayout() throws IOE
Path ociLayoutPath = layoutPath.resolve("testShouldCopyIndexWithPlatformFilterFromRegistryIntoOciLayout");
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
ContainerRef containerRef =
- ContainerRef.parse("%s/library/platform-filter-source".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/platform-filter-source", this.registry.getRegistry()));
// Push two manifests with different content, one per platform
Path fileAmd64 = blobDir.resolve("platform-filter-amd64.txt");
@@ -1062,7 +1061,7 @@ void testShouldCopyFromOciLayoutIntoOciLayoutRecursive() throws IOException {
// Target
Path ociLayoutPath = layoutPath.resolve("testShouldCopyFromOciLayoutIntoOciLayoutRecursive");
- LayoutRef targetRef = LayoutRef.parse("%s".formatted(ociLayoutPath.toString()));
+ LayoutRef targetRef = LayoutRef.parse(String.format("%s", ociLayoutPath.toString()));
OCILayout target = OCILayout.builder().defaults(targetRef.getFolder()).build();
// Copy to oci layout
@@ -1092,7 +1091,7 @@ void testShouldCopyFromOciLayoutIntoOciLayoutNonRecursive() throws IOException {
// Target
Path ociLayoutPath = layoutPath.resolve("testShouldCopyFromOciLayoutIntoOciLayoutNonRecursive");
- LayoutRef targetRef = LayoutRef.parse("%s".formatted(ociLayoutPath.toString()));
+ LayoutRef targetRef = LayoutRef.parse(String.format("%s", ociLayoutPath.toString()));
OCILayout target = OCILayout.builder().defaults(targetRef.getFolder()).build();
// Copy to oci layout
@@ -1122,10 +1121,10 @@ void testShouldCopyRecursivelyArtifactFromRegistryIntoOciLayout() throws IOExcep
.build();
OCILayout ociLayout = OCILayout.Builder.builder().defaults(layoutPath).build();
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(ociLayout.getPath()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", ociLayout.getPath()));
- ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-recursive-oci-layout".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef = ContainerRef.parse(
+ String.format("%s/library/artifact-recursive-oci-layout", this.registry.getRegistry()));
Path file1 = blobDir.resolve("artifact-recursive-oci-layout.txt");
Path file2 = blobDir.resolve("artifact-recursive-oci-attached.txt");
Path file3 = blobDir.resolve("artifact-recursive-oci-attached2.txt");
@@ -1173,10 +1172,10 @@ void testShouldCopyImageIntoOciLayoutWithoutIndexAndTag() {
.build();
OCILayout ociLayout = OCILayout.Builder.builder().defaults(layoutPath).build();
- LayoutRef layoutRef = LayoutRef.parse("%s:the-tag".formatted(ociLayout.getPath()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:the-tag", ociLayout.getPath()));
ContainerRef containerRef =
- ContainerRef.parse("%s/library/image-no-index".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/image-no-index", this.registry.getRegistry()));
Layer layer1 = registry.pushBlob(containerRef, Layer.empty().getDataBytes());
Layer layer2 = registry.pushBlob(containerRef, "foobar".getBytes());
@@ -1251,10 +1250,10 @@ void testShouldCopyImageIntoOciLayoutWithIndex() {
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(layoutPathIndex).build();
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayout.getPath()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayout.getPath()));
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-image-pull".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-image-pull", this.registry.getRegistry()));
Layer layer1 = registry.pushBlob(containerRef, Layer.empty().getDataBytes());
Layer layer2 = registry.pushBlob(containerRef, "foobar".getBytes());
@@ -1323,10 +1322,10 @@ void testShouldCopyIntoOciLayoutWithBlobConfig() throws IOException {
.build();
OCILayout ociLayout = OCILayout.Builder.builder().defaults(layoutPath).build();
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(ociLayout.getPath()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", ociLayout.getPath()));
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-oci-layout".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-oci-layout", this.registry.getRegistry()));
Path file1 = blobDir.resolve("artifact-oci-layout.txt");
Files.writeString(file1, "artifact-oci-layout");
@@ -1431,7 +1430,7 @@ void pullArtifactShouldRejectInvalidTitleAnnotation() throws IOException {
Path artifactFile = blobDir.resolve("safe.txt");
Files.writeString(artifactFile, "safe content");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
ociLayout.pushArtifact(
@@ -1531,7 +1530,7 @@ void shouldPushAndPullArtifactViaTagInTarBackedLayout() throws IOException {
Path artifactFile = blobDir.resolve("hello-tar.txt");
Files.writeString(artifactFile, "hello from tar");
- LayoutRef ref = LayoutRef.parse("%s:latest".formatted(tarFile.toString()));
+ LayoutRef ref = LayoutRef.parse(String.format("%s:latest", tarFile.toString()));
OCILayout ociLayout = OCILayout.Builder.builder().defaults(tarFile).build();
// Act: push
@@ -1543,7 +1542,7 @@ void shouldPushAndPullArtifactViaTagInTarBackedLayout() throws IOException {
// Re-open the layout from the same tar (simulate a second process)
OCILayout reopened = OCILayout.Builder.builder().defaults(tarFile).build();
- LayoutRef reopenedRef = LayoutRef.parse("%s:latest".formatted(tarFile.toString()));
+ LayoutRef reopenedRef = LayoutRef.parse(String.format("%s:latest", tarFile.toString()));
// Pull the artifact
Path pullDir = extractDir.resolve("tar-pull-out");
@@ -1561,7 +1560,7 @@ void shouldListTagsFromTarBackedLayout() throws IOException {
// Open the pre-built artifact.tar fixture
Path tarFile = Path.of("src/test/resources/oci/artifact.tar");
OCILayout ociLayout = OCILayout.Builder.builder().defaults(tarFile).build();
- LayoutRef ref = LayoutRef.parse("%s:latest".formatted(tarFile.toString()));
+ LayoutRef ref = LayoutRef.parse(String.format("%s:latest", tarFile.toString()));
Tags tags = ociLayout.getTags(ref);
assertEquals(1, tags.tags().size());
@@ -1573,7 +1572,7 @@ void shouldGetReferrersFromTarBackedLayout() throws IOException {
// Open the pre-built subject.tar fixture (has one referrer)
Path tarFile = Path.of("src/test/resources/oci/subject.tar");
OCILayout ociLayout = OCILayout.Builder.builder().defaults(tarFile).build();
- LayoutRef ref = LayoutRef.parse("%s:latest".formatted(tarFile.toString()));
+ LayoutRef ref = LayoutRef.parse(String.format("%s:latest", tarFile.toString()));
Referrers referrers = ociLayout.getReferrers(ref, null);
assertEquals(1, referrers.getManifests().size());
@@ -1587,7 +1586,7 @@ void shouldPullArtifactFromTarFixture() throws IOException {
// Open the pre-built artifact.tar fixture
Path tarFile = Path.of("src/test/resources/oci/artifact.tar");
OCILayout ociLayout = OCILayout.Builder.builder().defaults(tarFile).build();
- LayoutRef ref = LayoutRef.parse("%s:latest".formatted(tarFile.toString()));
+ LayoutRef ref = LayoutRef.parse(String.format("%s:latest", tarFile.toString()));
Path pullDir = extractDir.resolve("tar-fixture-pull");
Files.createDirectories(pullDir);
@@ -1601,12 +1600,12 @@ void shouldPullArtifactFromTarFixture() throws IOException {
void shouldReopenExistingTarAndPushAdditionalManifest() throws IOException {
// First session: create a tar-backed layout and push one manifest
Path tarFile = layoutPath.resolve("reopen-test.tar");
- LayoutRef ref1 = LayoutRef.parse("%s:v1".formatted(tarFile.toString()));
+ LayoutRef ref1 = LayoutRef.parse(String.format("%s:v1", tarFile.toString()));
OCILayout session1 = OCILayout.Builder.builder().defaults(tarFile).build();
session1.pushManifest(ref1, Manifest.empty().withConfig(Config.empty()));
// Second session: reopen the same tar and push another manifest
- LayoutRef ref2 = LayoutRef.parse("%s:v2".formatted(tarFile.toString()));
+ LayoutRef ref2 = LayoutRef.parse(String.format("%s:v2", tarFile.toString()));
OCILayout session2 = OCILayout.Builder.builder().defaults(tarFile).build();
Manifest m2 = Manifest.empty().withConfig(Config.empty()).withAnnotations(Map.of("version", "2"));
session2.pushManifest(ref2, m2);
@@ -1642,7 +1641,7 @@ void testShouldCopyArtifactFromRegistryIntoTarBackedOciLayout() throws IOExcepti
LayoutRef layoutRef = LayoutRef.of(ociLayout);
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-oci-layout-tar".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-oci-layout-tar", this.registry.getRegistry()));
Path file1 = blobDir.resolve("artifact-oci-layout-tar.txt");
Files.writeString(file1, "artifact-oci-layout-tar");
@@ -1676,10 +1675,10 @@ void testShouldCopyArtifactRecursivelyFromRegistryIntoTarBackedOciLayout() throw
Path tarFile = layoutPath.resolve("copy-deep.tar");
OCILayout ociLayout = OCILayout.Builder.builder().defaults(tarFile).build();
- LayoutRef layoutRef = LayoutRef.parse("%s".formatted(ociLayout.getPath()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s", ociLayout.getPath()));
ContainerRef containerRef = ContainerRef.parse(
- "%s/library/artifact-recursive-oci-layout-tar".formatted(this.registry.getRegistry()));
+ String.format("%s/library/artifact-recursive-oci-layout-tar", this.registry.getRegistry()));
Path file1 = blobDir.resolve("artifact-recursive-oci-layout-tar.txt");
Path file2 = blobDir.resolve("artifact-recursive-oci-attached-tar.txt");
Path file3 = blobDir.resolve("artifact-recursive-oci-attached2-tar.txt");
@@ -1723,7 +1722,7 @@ void shouldGarbageCollectReturnEmptyWhenNoBlobsAreOrphaned() throws IOException
Path artifactFile = blobDir.resolve("gc-no-orphan.txt");
Files.writeString(artifactFile, "no-orphan");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
ociLayout.pushArtifact(
@@ -1747,7 +1746,7 @@ void shouldGarbageCollectRemoveOrphanedBlob() throws IOException {
Path orphanFile = blobDir.resolve("gc-orphan-extra.txt");
Files.writeString(orphanFile, "orphaned-blob-content");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
ociLayout.pushArtifact(
@@ -1782,7 +1781,7 @@ void shouldGarbageCollectMultipleOrphanedBlobs() throws IOException {
Path artifactFile = blobDir.resolve("gc-multi-orphan.txt");
Files.writeString(artifactFile, "referenced-multi");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
ociLayout.pushArtifact(
@@ -1826,8 +1825,8 @@ void shouldGarbageCollectKeepAllBlobsAfterMultipleManifests() throws IOException
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
- LayoutRef ref1 = LayoutRef.parse("%s:v1".formatted(ociLayoutPath.toString()));
- LayoutRef ref2 = LayoutRef.parse("%s:v2".formatted(ociLayoutPath.toString()));
+ LayoutRef ref1 = LayoutRef.parse(String.format("%s:v1", ociLayoutPath.toString()));
+ LayoutRef ref2 = LayoutRef.parse(String.format("%s:v2", ociLayoutPath.toString()));
ociLayout.pushArtifact(ref1, ArtifactType.from("foo/bar"), Annotations.empty(), LocalPath.of(file1));
ociLayout.pushArtifact(ref2, ArtifactType.from("foo/bar"), Annotations.empty(), LocalPath.of(file2));
@@ -1849,7 +1848,7 @@ void shouldGarbageCollectKeepReferrerBlobsWhenCopiedDeep() throws IOException {
Files.writeString(mainFile, "main-artifact");
Files.writeString(attachFile, "attached-artifact");
- LayoutRef layoutRef = LayoutRef.parse("%s:latest".formatted(ociLayoutPath.toString()));
+ LayoutRef layoutRef = LayoutRef.parse(String.format("%s:latest", ociLayoutPath.toString()));
OCILayout ociLayout =
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
@@ -1901,8 +1900,8 @@ void shouldGarbageCollectWithNestedIndex() throws IOException {
OCILayout.Builder.builder().defaults(ociLayoutPath).build();
// Push two independent manifests (without a top-level tag so they get digest-only entries)
- LayoutRef ref1 = LayoutRef.parse("%s".formatted(ociLayoutPath.toString()));
- LayoutRef ref2 = LayoutRef.parse("%s".formatted(ociLayoutPath.toString()));
+ LayoutRef ref1 = LayoutRef.parse(String.format("%s", ociLayoutPath.toString()));
+ LayoutRef ref2 = LayoutRef.parse(String.format("%s", ociLayoutPath.toString()));
Manifest manifest1 = ociLayout.pushArtifact(
ref1, ArtifactType.from("foo/bar"), Annotations.empty(), LocalPath.of(file1, "text/plain"));
Manifest manifest2 = ociLayout.pushArtifact(
@@ -1912,7 +1911,7 @@ void shouldGarbageCollectWithNestedIndex() throws IOException {
assertNotNull(manifest1.getDescriptor(), "Manifest 1 descriptor should not be null");
assertNotNull(manifest2.getDescriptor(), "Manifest 2 descriptor should not be null");
Index nestedIndex = Index.fromManifests(List.of(manifest1.getDescriptor(), manifest2.getDescriptor()));
- LayoutRef indexRef = LayoutRef.parse("%s:multi".formatted(ociLayoutPath.toString()));
+ LayoutRef indexRef = LayoutRef.parse(String.format("%s:multi", ociLayoutPath.toString()));
Index pushedIndex = ociLayout.pushIndex(indexRef, nestedIndex);
// Collect the digests that must survive GC
diff --git a/src/test/java/land/oras/PlatformTest.java b/src/test/java/land/oras/PlatformTest.java
index 52455d5a..1dd3f86b 100644
--- a/src/test/java/land/oras/PlatformTest.java
+++ b/src/test/java/land/oras/PlatformTest.java
@@ -112,26 +112,14 @@ void shouldTestUnknownPlatform() {
@Test
void shouldReadFromJson() {
// language=json
- String json =
- """
- {
- "architecture": "amd64",
- "os": "linux"
- }
- """;
+ String json = "{\n" + " \"architecture\": \"amd64\",\n" + " \"os\": \"linux\"\n" + "}\n";
Platform platform = JsonUtils.fromJson(json, Platform.class);
assertEquals("amd64", platform.architecture());
assertEquals("linux", platform.os());
assertNull(platform.variant());
assertEquals(Platform.linuxAmd64(), platform);
- json =
- """
- {
- "architecture": "unknown",
- "os": "unknown"
- }
- """;
+ json = "{\n" + " \"architecture\": \"unknown\",\n" + " \"os\": \"unknown\"\n" + "}\n";
platform = JsonUtils.fromJson(json, Platform.class);
assertEquals(Platform.unknown(), platform);
}
@@ -139,17 +127,14 @@ void shouldReadFromJson() {
@Test
void shouldReadFromJsonWithOptionalValues() {
// language=json
- String json =
- """
- {
- "architecture": "amd64",
- "variant": "v8",
- "os.features": ["linux-gnu"],
- "os.version": "1.0",
- "features": ["sse4", "avx2"],
- "os": "linux"
- }
- """;
+ String json = "{\n"
+ + " \"architecture\": \"amd64\",\n"
+ + " \"variant\": \"v8\",\n"
+ + " \"os.features\": [\"linux-gnu\"],\n"
+ + " \"os.version\": \"1.0\",\n"
+ + " \"features\": [\"sse4\", \"avx2\"],\n"
+ + " \"os\": \"linux\"\n"
+ + "}\n";
Platform platform = JsonUtils.fromJson(json, Platform.class);
assertEquals("amd64", platform.architecture());
assertEquals("linux", platform.os());
@@ -164,13 +149,7 @@ void shouldReadFromJsonWithOptionalValues() {
.withVariant("v8"),
platform);
- json =
- """
- {
- "architecture": "unknown",
- "os": "unknown"
- }
- """;
+ json = "{\n" + " \"architecture\": \"unknown\",\n" + " \"os\": \"unknown\"\n" + "}\n";
platform = JsonUtils.fromJson(json, Platform.class);
assertEquals(Platform.unknown(), platform);
}
diff --git a/src/test/java/land/oras/PublicECRITCase.java b/src/test/java/land/oras/PublicECRITCase.java
index bbc29b6a..f315d541 100644
--- a/src/test/java/land/oras/PublicECRITCase.java
+++ b/src/test/java/land/oras/PublicECRITCase.java
@@ -41,9 +41,7 @@ class PublicECRITCase {
void shouldDetermineEffectiveRegistryWithUnqualifiedSettings() throws Exception {
// language=toml
- String config = """
- unqualified-search-registries = ["public.ecr.aws"]
- """;
+ String config = "unqualified-search-registries = [\"public.ecr.aws\"]\n";
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -60,12 +58,9 @@ void shouldDetermineEffectiveRegistryWithUnqualifiedSettings() throws Exception
void shouldRewriteDockerIOToPublicECR() throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- prefix = "docker.io/library"
- location = "public.ecr.aws/docker/library"
- """;
+ String config = "[[registry]]\n"
+ + "prefix = \"docker.io/library\"\n"
+ + "location = \"public.ecr.aws/docker/library\"\n";
TestUtils.createRegistriesConfFile(homeDir, config);
diff --git a/src/test/java/land/oras/ReferrersTest.java b/src/test/java/land/oras/ReferrersTest.java
index 105bb05c..4fe30f0c 100644
--- a/src/test/java/land/oras/ReferrersTest.java
+++ b/src/test/java/land/oras/ReferrersTest.java
@@ -33,22 +33,20 @@ class ReferrersTest {
private String descriptor() {
// language=json
- return """
- {
- "mediaType": "application/vnd.oci.image.manifest.v1+json",
- "digest": "sha256:09c8ec8bf0d43a250ba7fed2eb6f242935b2987be5ed921ee06c93008558f980",
- "size": 838,
- "annotations": {
- "com.docker.official-images.bashbrew.arch": "riscv64",
- "vnd.docker.reference.digest": "sha256:1de5eb4a9a6735adb46b2c9c88674c0cfba3444dd4ac2341b3babf1261700529",
- "vnd.docker.reference.type": "attestation-manifest"
- },
- "platform": {
- "architecture": "unknown",
- "os": "unknown"
- }
- }
- """;
+ return "{\n"
+ + " \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n"
+ + " \"digest\": \"sha256:09c8ec8bf0d43a250ba7fed2eb6f242935b2987be5ed921ee06c93008558f980\",\n"
+ + " \"size\": 838,\n"
+ + " \"annotations\": {\n"
+ + " \"com.docker.official-images.bashbrew.arch\": \"riscv64\",\n"
+ + " \"vnd.docker.reference.digest\": \"sha256:1de5eb4a9a6735adb46b2c9c88674c0cfba3444dd4ac2341b3babf1261700529\",\n"
+ + " \"vnd.docker.reference.type\": \"attestation-manifest\"\n"
+ + " },\n"
+ + " \"platform\": {\n"
+ + " \"architecture\": \"unknown\",\n"
+ + " \"os\": \"unknown\"\n"
+ + " }\n"
+ + "}\n";
}
@Test
diff --git a/src/test/java/land/oras/RegistryMirrorTest.java b/src/test/java/land/oras/RegistryMirrorTest.java
index 2d1f8e55..ec69e488 100644
--- a/src/test/java/land/oras/RegistryMirrorTest.java
+++ b/src/test/java/land/oras/RegistryMirrorTest.java
@@ -70,21 +70,19 @@ void shouldFetchManifestViaMirrorWhenOriginalIsDown(@TempDir Path blobDir) throw
// mirror 1: localhost:59999 (down, connection refused)
// mirror 2: the running mirrorUp container
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "localhost:59999"
- insecure = true
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"localhost:59999\"\n"
+ + "insecure = true\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -107,21 +105,19 @@ void shouldPullArtifactViaMirrorWhenOriginalIsDown(@TempDir Path blobDir, @TempD
setupRegistry.pushArtifact(mirrorArtifact, LocalPath.of(testFile));
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "localhost:59999"
- insecure = true
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"localhost:59999\"\n"
+ + "insecure = true\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -147,17 +143,15 @@ void shouldFetchManifestViaInsecurePathPrefixedMirror(@TempDir Path blobDir) thr
// Mirror location includes a path prefix → covers mirrorLocation.contains("/") branch.
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "%s/prefix"
- insecure = true
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s/prefix\"\n"
+ + "insecure = true\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -182,21 +176,19 @@ void shouldFetchBlobViaMirrorWhenOriginalIsDown(@TempDir Path blobDir, @TempDir
Layer layer = setupRegistry.pushBlob(mirrorRef, blobContent);
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "localhost:59999"
- insecure = true
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"localhost:59999\"\n"
+ + "insecure = true\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -241,21 +233,19 @@ void shouldFetchManifestViaUnqualifiedReference(@TempDir Path blobDir) throws Ex
// Configure docker.io with a mirror — unqualified refs resolve to docker.io by default
// language=toml
- String registriesConf =
- """
- short-name-mode = "disabled"
-
- unqualified-search-registries = ["docker.io"]
-
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "short-name-mode = \"disabled\"\n"
+ + "\n"
+ + "unqualified-search-registries = [\"docker.io\"]\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -286,20 +276,18 @@ void shouldResolveUnqualifiedRegistryViaFallbackWhenMirrorIsDown(@TempDir Path b
// is actually configured as the search registry below. The mirror here is unreachable: resolution
// must catch that failure and fall back to probing the search registry directly.
// language=toml
- String registriesConf =
- """
- short-name-mode = "disabled"
-
- unqualified-search-registries = ["%s"]
-
- [[registry]]
- prefix = "docker.io"
-
- [[registry.mirror]]
- location = "localhost:59999"
- insecure = true
- """
- .formatted(searchRegistry);
+ String registriesConf = String.format(
+ "short-name-mode = \"disabled\"\n"
+ + "\n"
+ + "unqualified-search-registries = [\"%s\"]\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"localhost:59999\"\n"
+ + "insecure = true\n",
+ searchRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -323,20 +311,18 @@ void shouldThrowWhenMirrorIsDownAndSearchRegistryLacksArtifact() throws Exceptio
String searchRegistry = mirrorUp.getRegistry();
// language=toml
- String registriesConf =
- """
- short-name-mode = "disabled"
-
- unqualified-search-registries = ["%s"]
-
- [[registry]]
- prefix = "docker.io"
-
- [[registry.mirror]]
- location = "localhost:59999"
- insecure = true
- """
- .formatted(searchRegistry);
+ String registriesConf = String.format(
+ "short-name-mode = \"disabled\"\n"
+ + "\n"
+ + "unqualified-search-registries = [\"%s\"]\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"localhost:59999\"\n"
+ + "insecure = true\n",
+ searchRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -349,7 +335,7 @@ void shouldThrowWhenMirrorIsDownAndSearchRegistryLacksArtifact() throws Exceptio
e.getMessage()
.startsWith("Container reference library/does-not-exist:v1 is unqualified"
+ " and cannot be found in any of the unqualified search registries"),
- "Wrong exception message: got '%s'".formatted(e.getMessage()));
+ String.format("Wrong exception message: got '%s'", e.getMessage()));
});
}
@@ -365,18 +351,16 @@ void shouldSkipDigestOnlyMirrorWhenPullingByTag(@TempDir Path blobDir) throws Ex
// Mirror configured as digest-only — a tag-based pull must NOT use it
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- pull-from-mirror = "digest-only"
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n"
+ + "pull-from-mirror = \"digest-only\"\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -407,18 +391,16 @@ void shouldUseDigestOnlyMirrorWhenPullingByDigest(@TempDir Path blobDir) throws
String digest = pushed.getDescriptor().getDigest();
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- pull-from-mirror = "digest-only"
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n"
+ + "pull-from-mirror = \"digest-only\"\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -447,18 +429,16 @@ void shouldApplyMirrorByDigestOnly(@TempDir Path blobDir) throws Exception {
String digest = pushed.getDescriptor().getDigest();
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
- mirror-by-digest-only = true
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- """
- .formatted(mirrorRegistry);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "mirror-by-digest-only = true\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirrorRegistry);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
diff --git a/src/test/java/land/oras/RegistryTest.java b/src/test/java/land/oras/RegistryTest.java
index e180a5e2..0a394254 100644
--- a/src/test/java/land/oras/RegistryTest.java
+++ b/src/test/java/land/oras/RegistryTest.java
@@ -83,10 +83,7 @@ void before() {
void shouldThrowIfUnableToFindOnAnyUnQualifiedSearchRegistry(@TempDir Path homeDir) throws Exception {
// language=toml
- String config = """
- unqualified-search-registries = ["%s"]
- """
- .formatted(registry.getRegistry());
+ String config = String.format("unqualified-search-registries = [\"%s\"]\n", registry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -103,10 +100,8 @@ void shouldThrowIfUnableToFindOnAnyUnQualifiedSearchRegistry(@TempDir Path homeD
void shouldEnforceMultipleRegistriesWithDefaultEnforcingMode(@TempDir Path homeDir) throws Exception {
// language=toml
- String config = """
- unqualified-search-registries = ["%s", "localhost:5000"]
- """
- .formatted(registry.getRegistry());
+ String config =
+ String.format("unqualified-search-registries = [\"%s\", \"localhost:5000\"]\n", registry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -119,7 +114,7 @@ void shouldEnforceMultipleRegistriesWithDefaultEnforcingMode(@TempDir Path homeD
e.getMessage()
.startsWith(
"Short name mode is set to ENFORCING/PERMISSION but multiple unqualified registries are configured"),
- "Wrong exception message: got '%s'".formatted(e.getMessage()));
+ String.format("Wrong exception message: got '%s'", e.getMessage()));
});
}
@@ -128,12 +123,9 @@ void shouldEnforceMultipleRegistriesWithDefaultEnforcingMode(@TempDir Path homeD
void shouldAllowMultipleRegistriesWithDisabledEnforcingMode(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- short-name-mode = "disabled"
- unqualified-search-registries = ["%s", "localhost:5000"]
- """
- .formatted(registry.getRegistry());
+ String config = String.format(
+ "short-name-mode = \"disabled\"\n" + "unqualified-search-registries = [\"%s\", \"localhost:5000\"]\n",
+ registry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -151,12 +143,9 @@ void shouldAllowMultipleRegistriesWithDisabledEnforcingMode(@TempDir Path homeDi
void shouldEnforceMultipleRegistriesWithPermissiveEnforcingMode(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- short-name-mode = "permissive"
- unqualified-search-registries = ["%s", "localhost:5000"]
- """
- .formatted(registry.getRegistry());
+ String config = String.format(
+ "short-name-mode = \"permissive\"\n" + "unqualified-search-registries = [\"%s\", \"localhost:5000\"]\n",
+ registry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
@@ -169,7 +158,7 @@ void shouldEnforceMultipleRegistriesWithPermissiveEnforcingMode(@TempDir Path ho
e.getMessage()
.startsWith(
"Short name mode is set to ENFORCING/PERMISSION but multiple unqualified registries are configured"),
- "Wrong exception message: got '%s'".formatted(e.getMessage()));
+ String.format("Wrong exception message: got '%s'", e.getMessage()));
});
}
@@ -191,13 +180,8 @@ void shouldListRepositories() {
void shouldListRepositoriesInsecure(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -290,13 +274,8 @@ void shouldCheckIfCanMount() throws IOException {
void shouldMountWithInsecureRegistry(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -399,9 +378,9 @@ void shouldPushAndGetBlobThenDeleteWithSha512() {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse(
- "%s/library/artifact-text@sha512:9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
- .formatted(this.registry.getRegistry()));
+ ContainerRef containerRef = ContainerRef.parse(String.format(
+ "%s/library/artifact-text@sha512:9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043",
+ this.registry.getRegistry()));
Layer layer = registry.pushBlob(containerRef, "hello".getBytes());
assertEquals(
"sha512:9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043",
@@ -427,7 +406,7 @@ void shouldPushAndGetBlobThenDeleteWithSha512() {
void shouldFailWithoutAuthentication() {
Registry registry = Registry.Builder.builder().insecure().build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-text".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-text", this.registry.getRegistry()));
assertThrows(OrasException.class, () -> {
registry.pushBlob(containerRef, "hello".getBytes());
});
@@ -437,7 +416,7 @@ void shouldFailWithoutAuthentication() {
void shouldPushUnsecure() {
Registry registry = Registry.Builder.builder().insecure().build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-text".formatted(this.unsecureRegistry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-text", this.unsecureRegistry.getRegistry()));
registry.pushBlob(containerRef, "hello".getBytes());
}
@@ -448,13 +427,8 @@ void shouldPushPullManifestsAndBlobsByUsingConfig(@TempDir Path homeDir) throws
ExecutorService customExecutor = Executors.newSingleThreadExecutor();
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -462,7 +436,7 @@ void shouldPushPullManifestsAndBlobsByUsingConfig(@TempDir Path homeDir) throws
.withExecutorService(customExecutor)
.build();
ContainerRef containerRef = ContainerRef.parse(
- "%s/library/artifact-text-manifest-blobs".formatted(this.unsecureRegistry.getRegistry()));
+ String.format("%s/library/artifact-text-manifest-blobs", this.unsecureRegistry.getRegistry()));
registry.pushBlob(containerRef, "hello".getBytes());
registry.pushBlob(containerRef, "other-hello".getBytes());
@@ -523,7 +497,7 @@ void shouldFailWithoutExistingAuthentication() {
Registry registry =
Registry.Builder.builder().defaults().withInsecure(true).build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-text".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-text", this.registry.getRegistry()));
assertThrows(
OrasException.class,
() -> {
@@ -539,7 +513,7 @@ void shouldUploadAndFetchBlobThenDelete() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-text".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-text", this.registry.getRegistry()));
Files.createFile(blobDir.resolve("temp.txt"));
Files.writeString(blobDir.resolve("temp.txt"), "hello");
Layer layer = registry.pushBlob(containerRef, blobDir.resolve("temp.txt"));
@@ -580,7 +554,7 @@ void shouldPushAndGetManifestThenDelete() {
// Empty manifest
ContainerRef containerRef =
- ContainerRef.parse("%s/library/empty-manifest".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/empty-manifest", this.registry.getRegistry()));
Layer emptyLayer = registry.pushBlob(containerRef, Layer.empty().getDataBytes());
Manifest emptyManifest = Manifest.empty().withLayers(List.of(Layer.fromDigest(emptyLayer.getDigest(), 2)));
Manifest pushedManifest = registry.pushManifest(containerRef, emptyManifest);
@@ -625,7 +599,8 @@ void shouldPushManifest() {
.build();
// Empty manifest
- ContainerRef containerRef = ContainerRef.parse("%s/library/empty-index".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef =
+ ContainerRef.parse(String.format("%s/library/empty-index", this.registry.getRegistry()));
Index emptyIndex = Index.fromManifests(List.of());
Index pushIndex = registry.pushIndex(containerRef, emptyIndex);
@@ -650,13 +625,8 @@ void shouldPushManifest() {
void shouldPushManifestWithRegistryConfig(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -664,7 +634,7 @@ void shouldPushManifestWithRegistryConfig(@TempDir Path homeDir) throws Exceptio
// Empty manifest
ContainerRef containerRef =
- ContainerRef.parse("%s/library/empty-index".formatted(this.unsecureRegistry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/empty-index", this.unsecureRegistry.getRegistry()));
Index emptyIndex = Index.fromManifests(List.of());
Index pushIndex = registry.pushIndex(containerRef, emptyIndex);
@@ -690,16 +660,14 @@ void shouldPushManifestWithRegistryConfig(@TempDir Path homeDir) throws Exceptio
void shouldPushManifestWithAlias(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
-
- [aliases]
- "my-library/my-namespace"="%s/test/bar"
- """
- .formatted(this.unsecureRegistry.getRegistry(), this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n"
+ + "\n"
+ + "[aliases]\n"
+ + "\"my-library/my-namespace\"=\"%s/test/bar\"\n",
+ this.unsecureRegistry.getRegistry(), this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -732,11 +700,9 @@ void shouldPushManifestWithAlias(@TempDir Path homeDir) throws Exception {
void shouldDetermineRegistryFromAlias(@TempDir Path homeDir) throws Exception {
// language=toml
- String config = """
- [aliases]
- "my-library/my-namespace"="localhost/test"
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[aliases]\n" + "\"my-library/my-namespace\"=\"localhost/test\"\n",
+ this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -781,7 +747,7 @@ void pullArtifactWithoutLayer() {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef1 = ContainerRef.parse("%s/empty-layers".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef1 = ContainerRef.parse(String.format("%s/empty-layers", this.registry.getRegistry()));
Config emptyConfig = Config.empty();
Manifest manifest1 = Manifest.empty().withConfig(emptyConfig);
registry.pushConfig(containerRef1, emptyConfig);
@@ -797,7 +763,8 @@ void pullArtifactShouldPullLayerWithTitleOnly() {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef1 = ContainerRef.parse("%s/empty-layers-title".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef1 =
+ ContainerRef.parse(String.format("%s/empty-layers-title", this.registry.getRegistry()));
Config emptyConfig = Config.empty();
Manifest manifest1 = Manifest.empty().withConfig(emptyConfig);
Layer layer = registry.pushBlob(containerRef1, "hello".getBytes(StandardCharsets.UTF_8))
@@ -817,7 +784,8 @@ void pullArtifactShouldPullLayerWithNoTitle() {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef1 = ContainerRef.parse("%s/no-layers-title".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef1 =
+ ContainerRef.parse(String.format("%s/no-layers-title", this.registry.getRegistry()));
Config emptyConfig = Config.empty();
Manifest manifest1 = Manifest.empty().withConfig(emptyConfig);
Layer layer = registry.pushBlob(containerRef1, "hello".getBytes(StandardCharsets.UTF_8))
@@ -839,8 +807,10 @@ void shouldPushComplexArtifactWithConfigMediaType() throws IOException {
.build();
// Manifest 1
- ContainerRef containerRef1 = ContainerRef.parse("%s/library/manifest1".formatted(this.registry.getRegistry()));
- ContainerRef containerRef2 = ContainerRef.parse("%s/library/manifest2".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef1 =
+ ContainerRef.parse(String.format("%s/library/manifest1", this.registry.getRegistry()));
+ ContainerRef containerRef2 =
+ ContainerRef.parse(String.format("%s/library/manifest2", this.registry.getRegistry()));
String content1 = "hello";
String content2 = "world";
@@ -895,8 +865,10 @@ void shouldPushComplexArtifactWithConfigArtifactType() throws IOException {
.build();
// Manifest 1
- ContainerRef containerRef1 = ContainerRef.parse("%s/library/manifest1".formatted(this.registry.getRegistry()));
- ContainerRef containerRef2 = ContainerRef.parse("%s/library/manifest2".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef1 =
+ ContainerRef.parse(String.format("%s/library/manifest1", this.registry.getRegistry()));
+ ContainerRef containerRef2 =
+ ContainerRef.parse(String.format("%s/library/manifest2", this.registry.getRegistry()));
String content1 = "hello";
String content2 = "world";
@@ -948,7 +920,7 @@ void shouldListReferrers(@TempDir Path homeDir) throws Exception {
// Manifest 1
ContainerRef containerRef1 =
- ContainerRef.parse("%s/library/manifest1:latest".formatted(this.unsecureRegistry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/manifest1:latest", this.unsecureRegistry.getRegistry()));
String content1 = "hello";
String content2 = "world";
@@ -976,10 +948,10 @@ void shouldListReferrers(@TempDir Path homeDir) throws Exception {
.withArtifactType(ArtifactType.from("text/plain"));
// Push second manifest with its digest
- ContainerRef containerRef2 = ContainerRef.parse("%s/library/manifest1@%s"
- .formatted(
- this.unsecureRegistry.getRegistry(),
- SupportedAlgorithm.SHA256.digest(manifest2.toJson().getBytes(StandardCharsets.UTF_8))));
+ ContainerRef containerRef2 = ContainerRef.parse(String.format(
+ "%s/library/manifest1@%s",
+ this.unsecureRegistry.getRegistry(),
+ SupportedAlgorithm.SHA256.digest(manifest2.toJson().getBytes(StandardCharsets.UTF_8))));
registry.pushManifest(containerRef2, manifest2);
// Pull via artifact 2
@@ -1025,13 +997,8 @@ void shouldListReferrers(@TempDir Path homeDir) throws Exception {
// Use config
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -1054,7 +1021,8 @@ void testShouldFailReferrerWithoutDigest() {
OrasException.class,
() -> {
registry.getReferrers(
- ContainerRef.parse("%s/library/manifest1".formatted(this.registry.getRegistry())), null);
+ ContainerRef.parse(String.format("%s/library/manifest1", this.registry.getRegistry())),
+ null);
},
"Digest is required to get referrers");
}
@@ -1068,7 +1036,7 @@ void testShouldCopyIndexOfIndexAndUseMounting() throws IOException {
.build();
ContainerRef containerSource =
- ContainerRef.parse("%s/library/artifact-mount-source".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-mount-source", this.registry.getRegistry()));
// Create files
Path file1 = blobDir.resolve("source1.txt");
@@ -1094,7 +1062,7 @@ void testShouldCopyIndexOfIndexAndUseMounting() throws IOException {
// Copy to other registry
ContainerRef containerTarget =
- ContainerRef.parse("%s/library/artifact-mount-target".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-mount-target", this.registry.getRegistry()));
CopyUtils.copy(
registry,
containerSource.withTag("index2"),
@@ -1114,7 +1082,7 @@ void testShouldCopyIndexOfIndexArtifactFromRegistryIntoRegistryWithDeepCopy() th
.build();
ContainerRef containerSource =
- ContainerRef.parse("%s/library/artifact-index-source".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-index-source", this.registry.getRegistry()));
// Create files
Path file1 = blobDir.resolve("source1.txt");
@@ -1142,7 +1110,7 @@ void testShouldCopyIndexOfIndexArtifactFromRegistryIntoRegistryWithDeepCopy() th
try (RegistryContainer otherRegistryContainer = new RegistryContainer()) {
otherRegistryContainer.start();
ContainerRef containerTarget = ContainerRef.parse(
- "%s/library/artifact-index-target".formatted(otherRegistryContainer.getRegistry()));
+ String.format("%s/library/artifact-index-target", otherRegistryContainer.getRegistry()));
CopyUtils.copy(
registry,
containerSource.withTag("index2"),
@@ -1163,7 +1131,7 @@ void testShouldCopySingleArtifactFromRegistryIntoRegistry() throws IOException {
.build();
ContainerRef containerSource =
- ContainerRef.parse("%s/library/artifact-source:foo".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-source:foo", this.registry.getRegistry()));
Path file1 = blobDir.resolve("source.txt");
Files.writeString(file1, "foobar");
@@ -1175,7 +1143,7 @@ void testShouldCopySingleArtifactFromRegistryIntoRegistry() throws IOException {
try (RegistryContainer otherRegistryContainer = new RegistryContainer()) {
otherRegistryContainer.start();
ContainerRef containerTarget = ContainerRef.parse(
- "%s/library/artifact-target:bar".formatted(otherRegistryContainer.getRegistry()));
+ String.format("%s/library/artifact-target:bar", otherRegistryContainer.getRegistry()));
CopyUtils.copy(registry, containerSource, registry, containerTarget, CopyUtils.CopyOptions.shallow());
registry.pullArtifact(containerTarget, artifactDir, true);
assertEquals("foobar", Files.readString(artifactDir.resolve("source.txt")));
@@ -1193,7 +1161,7 @@ void testShouldCopySingleFromDigest() throws IOException {
.build();
ContainerRef containerSource =
- ContainerRef.parse("%s/library/artifact-source:foo".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-source:foo", this.registry.getRegistry()));
Path file1 = blobDir.resolve("source.txt");
Files.writeString(file1, "foobar");
@@ -1204,8 +1172,8 @@ void testShouldCopySingleFromDigest() throws IOException {
// Copy to other registry
try (RegistryContainer otherRegistryContainer = new RegistryContainer()) {
otherRegistryContainer.start();
- ContainerRef containerTarget =
- ContainerRef.parse("%s/library/artifact-target".formatted(otherRegistryContainer.getRegistry()));
+ ContainerRef containerTarget = ContainerRef.parse(
+ String.format("%s/library/artifact-target", otherRegistryContainer.getRegistry()));
CopyUtils.copy(
registry,
containerSource.withDigest(manifest.getDigest()),
@@ -1224,16 +1192,14 @@ void testShouldCopySingleFromDigest() throws IOException {
void testShouldArtifactWithAlias(@TempDir Path homeDir) throws Exception {
// language=toml
- String config =
- """
- [aliases]
- "the-target" = "%s/test/artifact-target"
-
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry(), this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[aliases]\n"
+ + "\"the-target\" = \"%s/test/artifact-target\"\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ this.unsecureRegistry.getRegistry(), this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
// Copy to same registry
@@ -1268,25 +1234,22 @@ void testShouldCopyFromAliasToAlias(@TempDir Path homeDir) throws Exception {
otherRegistryContainer.start();
// language=toml
- String config =
- """
- [aliases]
- "the-source" = "%s/test/artifact-source"
- "the-target" = "%s/test/artifact-target"
-
- [[registry]]
- location = "%s"
- insecure = true
-
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(
- this.unsecureRegistry.getRegistry(),
- otherRegistryContainer.getRegistry(),
- this.unsecureRegistry.getRegistry(),
- otherRegistryContainer.getRegistry());
+ String config = String.format(
+ "[aliases]\n"
+ + "\"the-source\" = \"%s/test/artifact-source\"\n"
+ + "\"the-target\" = \"%s/test/artifact-target\"\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ this.unsecureRegistry.getRegistry(),
+ otherRegistryContainer.getRegistry(),
+ this.unsecureRegistry.getRegistry(),
+ otherRegistryContainer.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
// Copy to same registry
@@ -1324,7 +1287,7 @@ void testShouldCopyFromOciLayoutToRegistryNonRecursive() throws IOException {
.withInsecure(true)
.build();
ContainerRef targetRef =
- ContainerRef.parse("%s/library/copied-from-oci-layout".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/copied-from-oci-layout", this.registry.getRegistry()));
LayoutRef layoutRef = LayoutRef.parse("src/test/resources/oci/subject:latest");
OCILayout ociLayout =
@@ -1358,7 +1321,7 @@ void testShouldCopyFromOciLayoutToRegistryRecursive() throws IOException {
// Registry to copy
Registry registry = Registry.builder().withInsecure(true).build();
ContainerRef targetRef = ContainerRef.parse(
- "%s/library/copied-from-oci-layout-recursive".formatted(this.unsecureRegistry.getRegistry()));
+ String.format("%s/library/copied-from-oci-layout-recursive", this.unsecureRegistry.getRegistry()));
LayoutRef layoutRef = LayoutRef.parse("src/test/resources/oci/subject:latest");
OCILayout ociLayout =
@@ -1390,7 +1353,7 @@ void testNotFailToPullArtifactFromImage() {
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-image-pull".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-image-pull", this.registry.getRegistry()));
Layer emptyLayer = registry.pushBlob(containerRef, Layer.empty().getDataBytes());
@@ -1422,9 +1385,9 @@ void testShouldPushAndPullMinimalArtifact() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-full", this.registry.getRegistry()));
ContainerRef unknown =
- ContainerRef.parse("%s/library/artifact-full:unknown".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-full:unknown", this.registry.getRegistry()));
Path file1 = blobDir.resolve("file1.txt");
Files.writeString(file1, "foobar");
@@ -1495,7 +1458,7 @@ void testShouldPushMinimalArtifactThenAttachArtifactToManifest() throws IOExcept
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-maven".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-maven", this.registry.getRegistry()));
Path pomFile = blobDir.resolve("pom.xml");
Files.writeString(pomFile, "my pom file");
@@ -1536,7 +1499,7 @@ void testShouldPushMinimalArtifactThenAttachArtifactToIndex() throws IOException
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-maven".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-maven", this.registry.getRegistry()));
Path pomFile = blobDir.resolve("pom.xml");
Files.writeString(pomFile, "my pom file");
@@ -1585,7 +1548,7 @@ void testShouldArtifactWithAnnotations() throws IOException {
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-maven".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-maven", this.registry.getRegistry()));
Path pomFile = blobDir.resolve("pom.xml");
Files.writeString(pomFile, "my pom file");
@@ -1628,7 +1591,7 @@ void testShouldPushAndPullCompressedZipDirectoryButNotUnpackIt() throws IOExcept
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-zip".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-zip", this.registry.getRegistry()));
Path file1 = blobDir.resolve("file1.txt");
Path file2 = blobDir.resolve("file2.txt");
@@ -1653,7 +1616,7 @@ void testShouldPushAndPullCompressedZipDirectoryButNotUnpackIt() throws IOExcept
// Title must container filename
assertEquals(
- "%s.%s".formatted(blobDir.getFileName().toString(), SupportedCompression.ZIP.getFileExtension()),
+ String.format("%s.%s", blobDir.getFileName().toString(), SupportedCompression.ZIP.getFileExtension()),
annotations.get(Const.ANNOTATION_TITLE));
// Pull
@@ -1674,7 +1637,7 @@ void testShouldPushAndPullCompressedTarGzDirectory() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-full", this.registry.getRegistry()));
Path file1 = blobDir.resolve("file1.txt");
Path file2 = blobDir.resolve("file2.txt");
@@ -1719,7 +1682,7 @@ void testShouldPushAndPullUncompressedTarDirectoryWithAbsolutePath() throws IOEx
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-not-compressed".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-not-compressed", this.registry.getRegistry()));
Path file1 = blobDir.resolve("file1.txt");
Path file2 = blobDir.resolve("file2.txt");
@@ -1764,7 +1727,7 @@ void testShouldPushAndPullUncompressedTarDirectoryWithRelativePath() throws IOEx
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-relative-path".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-relative-path", this.registry.getRegistry()));
// Source
Manifest manifest = registry.pushArtifact(
@@ -1801,7 +1764,7 @@ void testShouldPushAndPullCompressedZstdDirectory() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-full", this.registry.getRegistry()));
Path file1 = blobDir.resolve("file1.txt");
Path file2 = blobDir.resolve("file2.txt");
@@ -1846,7 +1809,7 @@ void shouldFailToPushDirectoryWithInvalidCompression() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-full", this.registry.getRegistry()));
assertThrows(
OrasException.class,
@@ -1863,7 +1826,7 @@ void shouldPushAndGetBlobStreamSha256() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-stream", this.registry.getRegistry()));
// Create a file with test data to get accurate stream size
Path testFile = Files.createTempFile("test-data-", ".tmp");
@@ -1899,9 +1862,9 @@ void shouldPushAndGetBlobStreamWithSha512() throws IOException {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse(
- "%s/library/artifact-stream-sha512@sha512:ea0d8750d01f5fbd0da5d020d981b377fa2177874751063cb3da2117e481720774c0d985845a56c32ee6dde144901d92b2bdc8d0cb02373da141241aa2409859"
- .formatted(this.registry.getRegistry()));
+ ContainerRef containerRef = ContainerRef.parse(String.format(
+ "%s/library/artifact-stream-sha512@sha512:ea0d8750d01f5fbd0da5d020d981b377fa2177874751063cb3da2117e481720774c0d985845a56c32ee6dde144901d92b2bdc8d0cb02373da141241aa2409859",
+ this.registry.getRegistry()));
// Create a file with test data to get accurate stream size
Path testFile = Files.createTempFile("test-data-", ".tmp");
@@ -1938,7 +1901,7 @@ void shouldHandleExistingBlobInStreamPush() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-stream", this.registry.getRegistry()));
// Create test file
Path testFile = Files.createTempFile("test-data-", ".tmp");
@@ -1971,7 +1934,7 @@ void shouldHandleIOExceptionInStreamPush() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-stream", this.registry.getRegistry()));
// Create a failing input stream
InputStream failingStream = new InputStream() {
@@ -1994,7 +1957,7 @@ void shouldHandleNonExistentBlobInGetStreamSha256() {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-stream", this.registry.getRegistry()));
// Try to get non-existent blob
String nonExistentDigest = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
@@ -2010,7 +1973,7 @@ void shouldHandleNonExistentBlobInGetStreamSha512() {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-stream", this.registry.getRegistry()));
// Try to get non-existent blob
String nonExistentDigest =
@@ -2027,7 +1990,7 @@ void shouldHandleLargeStreamContent() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-stream", this.registry.getRegistry()));
// Create temp file with 5MB of random data
Path largeFile = Files.createTempFile("large-test-", ".tmp");
@@ -2055,7 +2018,7 @@ void shouldPushBlobChunkedFromPath() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-chunked-path".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-chunked-path", this.registry.getRegistry()));
// Create a file with known content
byte[] content = "hello chunked world".getBytes(StandardCharsets.UTF_8);
@@ -2085,8 +2048,8 @@ void shouldPushBlobChunkedFromPathWithSingleChunk() throws IOException {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-chunked-path-single".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef = ContainerRef.parse(
+ String.format("%s/library/artifact-chunked-path-single", this.registry.getRegistry()));
// Content that fits in one chunk
byte[] content = "hi".getBytes(StandardCharsets.UTF_8);
@@ -2108,7 +2071,7 @@ void shouldPushBlobChunkedFromInputStream() {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-chunked-stream".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-chunked-stream", this.registry.getRegistry()));
byte[] content = "hello chunked stream".getBytes(StandardCharsets.UTF_8);
String expectedDigest = SupportedAlgorithm.SHA256.digest(content);
@@ -2138,7 +2101,7 @@ void shouldPushLargeBlobChunkedFromPath() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-chunked-large".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-chunked-large", this.registry.getRegistry()));
// 1 MB of random content
byte[] content = new byte[1024 * 1024];
@@ -2168,7 +2131,7 @@ void shouldFailChunkedUploadWithMissingDigestOnInputStream() {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-chunked-err".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-chunked-err", this.registry.getRegistry()));
OrasException e = assertThrows(
OrasException.class,
@@ -2184,7 +2147,7 @@ void shouldFailChunkedUploadWithInvalidChunkSize() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-chunked-err".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/artifact-chunked-err", this.registry.getRegistry()));
byte[] content = "data".getBytes(StandardCharsets.UTF_8);
Path blobFile = blobDir.resolve("err.txt");
@@ -2207,13 +2170,8 @@ void shouldFailChunkedUploadWithInvalidChunkSize() throws IOException {
void shouldPushBlobChunkedFromPathViaInsecureRegistryConfig(@TempDir Path homeDir) throws Exception {
// Insecure config
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -2246,13 +2204,8 @@ void shouldPushBlobChunkedFromPathViaInsecureRegistryConfig(@TempDir Path homeDi
void shouldPushBlobChunkedFromStreamViaInsecureRegistryConfig(@TempDir Path homeDir) throws Exception {
// Insecure config
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(this.unsecureRegistry.getRegistry());
+ String config = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", this.unsecureRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, config);
TestUtils.withHome(homeDir, () -> {
@@ -2282,7 +2235,7 @@ void testShouldCopyIndexWithPlatformFilter() throws IOException {
.build();
ContainerRef containerSource =
- ContainerRef.parse("%s/library/multi-platform-source".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/multi-platform-source", this.registry.getRegistry()));
// Push two manifests with different content
Path fileAmd64 = blobDir.resolve("amd64.txt");
@@ -2304,8 +2257,8 @@ void testShouldCopyIndexWithPlatformFilter() throws IOException {
registry.pushIndex(containerSource.withTag("latest"), sourceIndex);
// Copy only linux/amd64 to target
- ContainerRef containerTarget =
- ContainerRef.parse("%s/library/multi-platform-target:latest".formatted(this.registry.getRegistry()));
+ ContainerRef containerTarget = ContainerRef.parse(
+ String.format("%s/library/multi-platform-target:latest", this.registry.getRegistry()));
CopyUtils.copy(
registry,
containerSource.withTag("latest"),
@@ -2340,7 +2293,7 @@ void testShouldThrowWhenPlatformFilterMatchesNothing() throws IOException {
.build();
ContainerRef containerSource =
- ContainerRef.parse("%s/library/no-match-source".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/no-match-source", this.registry.getRegistry()));
// Push a single manifest and build an index with linux/amd64
Path file = blobDir.resolve("no-match.txt");
@@ -2352,7 +2305,7 @@ void testShouldThrowWhenPlatformFilterMatchesNothing() throws IOException {
registry.pushIndex(containerSource.withTag("latest"), sourceIndex);
ContainerRef containerTarget =
- ContainerRef.parse("%s/library/no-match-target:latest".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/no-match-target:latest", this.registry.getRegistry()));
// Filtering for a platform not present in the index must throw
assertThrows(
@@ -2372,8 +2325,8 @@ void shouldPushArtifactWithChunkedOptions() throws IOException {
.defaults("myuser", "mypass")
.withInsecure(true)
.build();
- ContainerRef containerRef =
- ContainerRef.parse("%s/library/artifact-push-options-chunked".formatted(this.registry.getRegistry()));
+ ContainerRef containerRef = ContainerRef.parse(
+ String.format("%s/library/artifact-push-options-chunked", this.registry.getRegistry()));
Path file = blobDir.resolve("chunked-artifact.txt");
Files.writeString(file, "hello chunked artifact");
@@ -2398,7 +2351,7 @@ void shouldPushArtifactWithChunkedOptionsCustomChunkSizeAndArtifactType() throws
.withInsecure(true)
.build();
ContainerRef containerRef = ContainerRef.parse(
- "%s/library/artifact-push-options-chunked-type".formatted(this.registry.getRegistry()));
+ String.format("%s/library/artifact-push-options-chunked-type", this.registry.getRegistry()));
Path file = blobDir.resolve("chunked-artifact-type.txt");
Files.writeString(file, "chunked with artifact type");
@@ -2421,7 +2374,7 @@ void shouldPushArtifactWithChunkedOptionsAndAnnotations() throws IOException {
.withInsecure(true)
.build();
ContainerRef containerRef = ContainerRef.parse(
- "%s/library/artifact-push-options-chunked-annotations".formatted(this.registry.getRegistry()));
+ String.format("%s/library/artifact-push-options-chunked-annotations", this.registry.getRegistry()));
Path file = blobDir.resolve("chunked-artifact-annotations.txt");
Files.writeString(file, "chunked with annotations");
@@ -2461,7 +2414,7 @@ void shouldBlockPullWithRejectPolicy(@TempDir Path policyDir) throws IOException
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/policy-reject-test".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/policy-reject-test", this.registry.getRegistry()));
Path file = blobDir.resolve("policy-test.txt");
Files.writeString(file, "This artifact should be blocked");
@@ -2484,9 +2437,7 @@ void shouldBlockPullWithRejectPolicy(@TempDir Path policyDir) throws IOException
void shouldBlockPullWithPolicyFromPath() throws IOException {
// Create a temporary policy file with reject-all
Path policyFile = Files.createTempFile("policy", ".json");
- Files.writeString(policyFile, """
- {"default": [{"type": "reject"}]}
- """);
+ Files.writeString(policyFile, "{\"default\": [{\"type\": \"reject\"}]}\n");
// Create a registry with policy loaded from file path
Registry registryWithPolicy = Registry.Builder.builder()
@@ -2502,7 +2453,7 @@ void shouldBlockPullWithPolicyFromPath() throws IOException {
.build();
ContainerRef containerRef =
- ContainerRef.parse("%s/library/policy-path-test".formatted(this.registry.getRegistry()));
+ ContainerRef.parse(String.format("%s/library/policy-path-test", this.registry.getRegistry()));
Path file = blobDir.resolve("policy-path-test.txt");
Files.writeString(file, "This artifact should be blocked by policy from file");
@@ -2514,7 +2465,7 @@ void shouldBlockPullWithPolicyFromPath() throws IOException {
// Pull fails with policy from file - verify exact error message format
OrasException ex = assertThrows(OrasException.class, () -> registryWithPolicy.getManifest(containerRef));
String expectedMessage =
- "Access to container reference %s is blocked by registry configuration".formatted(containerRef);
+ String.format("Access to container reference %s is blocked by registry configuration", containerRef);
assertEquals(expectedMessage, ex.getMessage());
Files.deleteIfExists(policyFile);
@@ -2525,11 +2476,7 @@ private Path createRejectPolicy(Path tempDir) throws IOException {
Files.writeString(
output,
// language=json
- """
- {
- "default": [{"type": "reject"}]
- }
- """);
+ "{\n" + " \"default\": [{\"type\": \"reject\"}]\n" + "}\n");
return output;
}
@@ -2538,11 +2485,7 @@ private Path createAcceptAllPolicy(Path tempDir) throws IOException {
Files.writeString(
output,
// language=json
- """
- {
- "default": [{"type": "insecureAcceptAnything"}]
- }
- """);
+ "{\n" + " \"default\": [{\"type\": \"insecureAcceptAnything\"}]\n" + "}\n");
return output;
}
}
diff --git a/src/test/java/land/oras/RegistryTlsTest.java b/src/test/java/land/oras/RegistryTlsTest.java
index e67c6b43..1be78745 100644
--- a/src/test/java/land/oras/RegistryTlsTest.java
+++ b/src/test/java/land/oras/RegistryTlsTest.java
@@ -31,7 +31,6 @@
import java.security.KeyPair;
import java.util.List;
import java.util.Map;
-import javax.net.ssl.SSLHandshakeException;
import land.oras.exception.OrasException;
import land.oras.utils.SupportedAlgorithm;
import land.oras.utils.TlsUtils;
@@ -91,7 +90,7 @@ void shouldFailWithoutCaCertificate() {
Registry.builder().withRegistry(tlsRegistry.getRegistry()).build();
OrasException exception = assertThrows(OrasException.class, registry::getRepositories);
- assertInstanceOf(SSLHandshakeException.class, exception.getCause());
+ assertInstanceOf(IOException.class, exception.getCause());
}
@Test
@@ -102,7 +101,7 @@ void shouldFailWithWrongCaCertificate() {
.build();
OrasException exception = assertThrows(OrasException.class, registry::getRepositories);
- assertInstanceOf(SSLHandshakeException.class, exception.getCause());
+ assertInstanceOf(IOException.class, exception.getCause());
}
@Test
@@ -128,7 +127,7 @@ void shouldBeSecureAfterAsSecure() {
// asSecure() resets both insecure and skipTlsVerify — now proper TLS is enforced
Registry secureRegistry = skipVerifyRegistry.asSecure();
OrasException e = assertThrows(OrasException.class, secureRegistry::getRepositories);
- assertInstanceOf(SSLHandshakeException.class, e.getCause());
+ assertInstanceOf(IOException.class, e.getCause());
// Verify the same host works when the CA cert is provided
Registry secureWithCa = Registry.builder()
@@ -144,13 +143,8 @@ void shouldDowngradeToSecureWhenConfigExplicitlyNotInsecure(@TempDir Path homeDi
// Authoritative parent
// language=toml
- String registriesConf =
- """
- [[registry]]
- location = "%s"
- insecure = false
- """
- .formatted(tlsRegistry.getRegistry());
+ String registriesConf = String.format(
+ "[[registry]]\n" + "location = \"%s\"\n" + "insecure = false\n", tlsRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
@@ -173,11 +167,7 @@ void shouldUpgradeToSecureAllOperationsToSecureWhenConfigDefaultsToSecure(
// Registry is set to secure by default
// language=toml
- String registriesConf = """
- [[registry]]
- location = "%s"
- """
- .formatted(tlsRegistry.getRegistry());
+ String registriesConf = String.format("[[registry]]\n" + "location = \"%s\"\n", tlsRegistry.getRegistry());
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
Path testFile = blobDir.resolve("downgrade1.txt");
@@ -299,21 +289,19 @@ void shouldNotDowngradeSecureMirrorWhenRegistryLevelInsecureSetForMirrorHost(
// connection to plaintext HTTP. The HTTPS-only container would refuse a plaintext request, so
// a successful manifest fetch proves the connection stayed on HTTPS.
// language=toml
- String registriesConf =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "%s"
-
- [[registry]]
- prefix = "%s"
- location = "%s"
- insecure = true
- """
- .formatted(mirror, mirror, mirror);
+ String registriesConf = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "\n"
+ + "[[registry]]\n"
+ + "prefix = \"%s\"\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirror, mirror, mirror);
TestUtils.createRegistriesConfFile(homeDir, registriesConf);
diff --git a/src/test/java/land/oras/RegistryWireMockTest.java b/src/test/java/land/oras/RegistryWireMockTest.java
index 801b9b7e..1fdc5b39 100644
--- a/src/test/java/land/oras/RegistryWireMockTest.java
+++ b/src/test/java/land/oras/RegistryWireMockTest.java
@@ -104,15 +104,16 @@ void shouldPassBearerTokenWithExternalRequestedToken(WireMockRuntimeInfo wmRunti
.withHeader("Authorization", equalTo("Bearer invalid-token"))
.willReturn(WireMock.unauthorized()));
- registry.getTags(ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))));
+ registry.getTags(ContainerRef.parse(String.format(
+ "%s/library/artifact-text", wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))));
// Ensure it fail with invalid token
final Registry newRegistry = registry.withAuthToken("invalid-token");
OrasException e = assertThrows(
OrasException.class,
- () -> newRegistry.getTags(ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))));
+ () -> newRegistry.getTags(ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))));
assertEquals(401, e.getStatusCode());
}
@@ -132,8 +133,8 @@ void shouldFailToGetManifestOn403(WireMockRuntimeInfo wmRuntimeInfo) {
.withInsecure(true)
.build();
- ContainerRef containerRef =
- ContainerRef.parse("localhost:%d/library/some-artifact:latest".formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef containerRef = ContainerRef.parse(
+ String.format("localhost:%d/library/some-artifact:latest", wmRuntimeInfo.getHttpPort()));
OrasException exception = assertThrows(OrasException.class, () -> registry.getManifest(containerRef));
assertEquals(403, exception.getStatusCode());
}
@@ -145,12 +146,13 @@ void shouldRedirectWhenDownloadingBlob(WireMockRuntimeInfo wmRuntimeInfo) {
// Return data from wiremock
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/artifact-text/blobs/%s".formatted(digest)))
- .willReturn(WireMock.temporaryRedirect("http://localhost:%d/v2/library/artifact-text/blobs/sha256:other"
- .formatted(wmRuntimeInfo.getHttpPort()))));
+ wireMock.register(WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/artifact-text/blobs/%s", digest)))
+ .willReturn(WireMock.temporaryRedirect(String.format(
+ "http://localhost:%d/v2/library/artifact-text/blobs/sha256:other",
+ wmRuntimeInfo.getHttpPort()))));
// Return blob on new location
- wireMock.register(head(WireMock.urlEqualTo("/v2/library/artifact-text/blobs/%s".formatted(digest)))
+ wireMock.register(head(WireMock.urlEqualTo(String.format("/v2/library/artifact-text/blobs/%s", digest)))
.willReturn(WireMock.ok()));
wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/artifact-text/blobs/sha256:other"))
.willReturn(
@@ -163,7 +165,7 @@ void shouldRedirectWhenDownloadingBlob(WireMockRuntimeInfo wmRuntimeInfo) {
.build();
ContainerRef containerRef =
- ContainerRef.parse("localhost:%d/library/artifact-text".formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef.parse(String.format("localhost:%d/library/artifact-text", wmRuntimeInfo.getHttpPort()));
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
assertEquals("blob-data", new String(blob));
}
@@ -187,8 +189,8 @@ void mountBlobShouldReturnFalseOn202(WireMockRuntimeInfo wmRuntimeInfo) throws I
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse(
- "%s/test@sha512:12345".formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")));
+ ContainerRef containerRef = ContainerRef.parse(String.format(
+ "%s/test@sha512:12345", wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")));
assertFalse(registry.mountBlob(containerRef, containerRef), "Mount blob should return false");
}
@@ -213,9 +215,9 @@ void shouldRedirectWhenPushingBlob(WireMockRuntimeInfo wmRuntimeInfo) throws IOE
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse(
- "localhost:%d/library/artifact-redirect@sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
- .formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef containerRef = ContainerRef.parse(String.format(
+ "localhost:%d/library/artifact-redirect@sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
+ wmRuntimeInfo.getHttpPort()));
registry.pushBlob(containerRef, "hello".getBytes());
// Via file
@@ -234,12 +236,13 @@ void shouldNotSendAuthHeaderOnRedirectToDifferentDomain(WireMockRuntimeInfo wmRu
redirectTarget.start();
try {
- String redirectUrl = "http://localhost:%d/v2/other/blobs/sha256:other".formatted(redirectTarget.port());
+ String redirectUrl =
+ String.format("http://localhost:%d/v2/other/blobs/sha256:other", redirectTarget.port());
WireMock mainMock = wmRuntimeInfo.getWireMock();
// Main mock responds with redirect to different domain
- mainMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/artifact/blobs/%s".formatted(digest)))
+ mainMock.register(WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/artifact/blobs/%s", digest)))
.willReturn(WireMock.temporaryRedirect(redirectUrl)));
// Secondary server returns blob, we inspect headers here
@@ -258,7 +261,7 @@ void shouldNotSendAuthHeaderOnRedirectToDifferentDomain(WireMockRuntimeInfo wmRu
.build();
ContainerRef containerRef =
- ContainerRef.parse("localhost:%d/library/artifact".formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef.parse(String.format("localhost:%d/library/artifact", wmRuntimeInfo.getHttpPort()));
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
assertEquals("blob-data", new String(blob));
@@ -282,26 +285,20 @@ void shouldNotForwardParentCredentialsToInsecureMirror(WireMockRuntimeInfo wmRun
// WireMock server. The parent registry is configured with static basic-auth credentials — those
// credentials must NOT be forwarded to the mirror over plaintext HTTP.
// language=toml
- String config =
- """
- [[registry]]
- prefix = "localhost:59998"
- location = "localhost:59998"
-
- [[registry.mirror]]
- location = "%s"
- insecure = true
- """
- .formatted(mirrorHost);
+ String config = String.format(
+ "[[registry]]\n"
+ + "prefix = \"localhost:59998\"\n"
+ + "location = \"localhost:59998\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"%s\"\n"
+ + "insecure = true\n",
+ mirrorHost);
TestUtils.createRegistriesConfFile(homeDir4, config);
// language=json
String manifestJson =
- """
- {"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "config":{"mediaType":"application/vnd.oci.empty.v1+json",\
- "digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},\
- "layers":[]}""";
+ "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"config\":{\"mediaType\":\"application/vnd.oci.empty.v1+json\",\"digest\":\"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\",\"size\":2},\"layers\":[]}";
WireMock wireMock = wmRuntimeInfo.getWireMock();
String manifestPath = "/v2/library/cred-mirror/manifests/v1";
@@ -345,8 +342,9 @@ void shouldListTags(WireMockRuntimeInfo wmRuntimeInfo) {
.build();
// Test
- List tags = registry.getTags(ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))))
+ List tags = registry.getTags(ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))))
.tags();
// Assert
@@ -371,8 +369,9 @@ void shouldListTagsWithLimit(WireMockRuntimeInfo wmRuntimeInfo) {
// Test
List tags = registry.getTags(
- ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))),
+ ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))),
1,
null)
.tags();
@@ -388,13 +387,7 @@ void shouldListTagsWithConfig(WireMockRuntimeInfo wmRuntimeInfo) throws Exceptio
String registryAsString = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(registryAsString);
+ String config = String.format("[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", registryAsString);
TestUtils.createRegistriesConfFile(homeDir1, config);
// Return data from wiremock
@@ -407,7 +400,7 @@ void shouldListTagsWithConfig(WireMockRuntimeInfo wmRuntimeInfo) throws Exceptio
Registry registry =
Registry.Builder.builder().withAuthProvider(authProvider).build();
List tags = registry.getTags(
- ContainerRef.parse("%s/library/artifact-text-with-confg".formatted(registryAsString)))
+ ContainerRef.parse(String.format("%s/library/artifact-text-with-confg", registryAsString)))
.tags();
assertEquals(2, tags.size());
assertEquals("latest", tags.get(0));
@@ -415,7 +408,7 @@ void shouldListTagsWithConfig(WireMockRuntimeInfo wmRuntimeInfo) throws Exceptio
// With limit
tags = registry.getTags(
- ContainerRef.parse("%s/library/artifact-text-with-confg".formatted(registryAsString)),
+ ContainerRef.parse(String.format("%s/library/artifact-text-with-confg", registryAsString)),
1,
null)
.tags();
@@ -457,13 +450,7 @@ void shouldListRepositoryWithLocationConfig(WireMockRuntimeInfo wmRuntimeInfo) t
String registryAsString = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
// language=toml
- String config =
- """
- [[registry]]
- location = "%s"
- insecure = true
- """
- .formatted(registryAsString);
+ String config = String.format("[[registry]]\n" + "location = \"%s\"\n" + "insecure = true\n", registryAsString);
TestUtils.createRegistriesConfFile(homeDir2, config);
// Return data from wiremock
@@ -492,13 +479,7 @@ void shouldListRepositoryWithPrefixConfig(WireMockRuntimeInfo wmRuntimeInfo) thr
String registryAsString = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
// language=toml
- String config =
- """
- [[registry]]
- prefix = "%s"
- insecure = true
- """
- .formatted(registryAsString);
+ String config = String.format("[[registry]]\n" + "prefix = \"%s\"\n" + "insecure = true\n", registryAsString);
TestUtils.createRegistriesConfFile(homeDir3, config);
// Return data from wiremock
@@ -525,17 +506,15 @@ void shouldListRepositoryWithPrefixConfig(WireMockRuntimeInfo wmRuntimeInfo) thr
void shouldListTagsWithFileStoreAuth(WireMockRuntimeInfo wmRuntimeInfo) throws IOException {
// Auth file for current registry
- String authFile =
- """
- {
- "auths": {
- "localhost:%d": {
- "auth": "bXl1c2VyOm15cGFzcw=="
- }
- }
- }
- """
- .formatted(wmRuntimeInfo.getHttpPort());
+ String authFile = String.format(
+ "{\n"
+ + " \"auths\": {\n"
+ + " \"localhost:%d\": {\n"
+ + " \"auth\": \"bXl1c2VyOm15cGFzcw==\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ wmRuntimeInfo.getHttpPort());
Files.writeString(configDir.resolve("config.json"), authFile, StandardCharsets.UTF_8);
@@ -555,8 +534,9 @@ void shouldListTagsWithFileStoreAuth(WireMockRuntimeInfo wmRuntimeInfo) throws I
.build();
// Test
- List tags = registry.getTags(ContainerRef.parse("%s/library/artifact-text-store"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))))
+ List tags = registry.getTags(ContainerRef.parse(String.format(
+ "%s/library/artifact-text-store",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))))
.tags();
// Assert
@@ -579,12 +559,12 @@ void shouldHandle500Error(WireMockRuntimeInfo wmRuntimeInfo) {
Registry registry = Registry.Builder.builder().withInsecure(true).build();
// Now we should have a reference to container
- ContainerRef ref = ContainerRef.parse("%s/library/error-artifact".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/error-artifact", registryUrl));
OrasException exception = assertThrows(OrasException.class, () -> registry.getTags(ref));
assertEquals(500, exception.getStatusCode());
- ContainerRef ref2 = ContainerRef.parse("%s/library/error-artifact@sha256:1234".formatted(registryUrl));
+ ContainerRef ref2 = ContainerRef.parse(String.format("%s/library/error-artifact@sha256:1234", registryUrl));
OrasException exception2 = assertThrows(OrasException.class, () -> registry.fetchBlobDescriptor(ref2));
assertEquals(500, exception2.getStatusCode());
}
@@ -596,7 +576,7 @@ void shouldHandleTimeout(WireMockRuntimeInfo wmRuntimeInfo) {
String registryUrl = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
// Using here a unique container reference to avoid conflicts when running in parallel
- ContainerRef ref = ContainerRef.parse("%s/library/timeout-artifact".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/timeout-artifact", registryUrl));
// We Set up the stub for the timeout scenario
wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/timeout-artifact/tags/list"))
@@ -635,7 +615,7 @@ void shouldRetryBlobUpload(WireMockRuntimeInfo wmRuntimeInfo) throws IOException
Registry registry =
Registry.Builder.builder().withInsecure(true).withRetryDelay(0).build();
- ContainerRef ref = ContainerRef.parse("%s/library/artifact-text".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/artifact-text", registryUrl));
Path testFile = configDir.resolve("test-data.temp");
Files.writeString(testFile, "Test Content");
@@ -679,13 +659,14 @@ void shouldRefreshExpiredToken(WireMockRuntimeInfo wmRuntimeInfo) {
// Return data from wiremock
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/refresh-token/blobs/%s".formatted(digest)))
+ wireMock.register(WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/refresh-token/blobs/%s", digest)))
.inScenario("get token")
.willReturn(WireMock.forbidden()
.withHeader(
Const.WWW_AUTHENTICATE_HEADER,
- "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/refresh-token:pull\""
- .formatted(wmRuntimeInfo.getHttpPort()))));
+ String.format(
+ "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/refresh-token:pull\"",
+ wmRuntimeInfo.getHttpPort()))));
// Return token
wireMock.register(WireMock.any(
@@ -696,7 +677,7 @@ void shouldRefreshExpiredToken(WireMockRuntimeInfo wmRuntimeInfo) {
new HttpClient.TokenResponse("fake-token", "access-token", null, 300, ZonedDateTime.now())))));
// On the second call we return ok
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/refresh-token/blobs/%s".formatted(digest)))
+ wireMock.register(WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/refresh-token/blobs/%s", digest)))
.inScenario("get token")
.whenScenarioStateIs("get")
.willReturn(
@@ -709,7 +690,7 @@ void shouldRefreshExpiredToken(WireMockRuntimeInfo wmRuntimeInfo) {
.build();
ContainerRef containerRef =
- ContainerRef.parse("localhost:%d/library/refresh-token".formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef.parse(String.format("localhost:%d/library/refresh-token", wmRuntimeInfo.getHttpPort()));
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
assertEquals("blob-data", new String(blob));
@@ -722,7 +703,7 @@ void shouldRefreshExpiredToken(WireMockRuntimeInfo wmRuntimeInfo) {
Const.METRIC_TAG_SERVICE,
"localhost",
Const.METRIC_TAG_REALM,
- "http://localhost:%d/token".formatted(wmRuntimeInfo.getHttpPort()))
+ String.format("http://localhost:%d/token", wmRuntimeInfo.getHttpPort()))
.count(),
"Token refresh counter should be 1 after one token refresh");
assertEquals(
@@ -790,7 +771,7 @@ void shouldHandleRateLimitingResponse(WireMockRuntimeInfo wmRuntimeInfo) {
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/rate-limited".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/rate-limited", registryUrl));
// Verify that a 429 status code is thrown as an OrasException
OrasException exception = assertThrows(OrasException.class, () -> registry.getTags(ref));
@@ -805,19 +786,20 @@ void shouldFollowRedirectOnBlobFetch(WireMockRuntimeInfo wmRuntimeInfo) {
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
// Setup WireMock to redirect to a new location
- String redirectUrl = "http://%s/v2/library/redirect-blob/blobs/redirected/%s".formatted(registryUrl, digest);
- wireMock.register(get(urlEqualTo("/v2/library/redirect-blob/blobs/%s".formatted(digest)))
+ String redirectUrl =
+ String.format("http://%s/v2/library/redirect-blob/blobs/redirected/%s", registryUrl, digest);
+ wireMock.register(get(urlEqualTo(String.format("/v2/library/redirect-blob/blobs/%s", digest)))
.willReturn(aResponse().withStatus(307).withHeader("Location", redirectUrl)));
// Setup WireMock to serve blob at redirected location
- wireMock.register(get(urlEqualTo("/v2/library/redirect-blob/blobs/redirected/%s".formatted(digest)))
+ wireMock.register(get(urlEqualTo(String.format("/v2/library/redirect-blob/blobs/redirected/%s", digest)))
.willReturn(aResponse()
.withStatus(200)
.withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digest)
.withBody("blob-data")));
// Setup HEAD request for validation
- wireMock.register(head(urlEqualTo("/v2/library/redirect-blob/blobs/%s".formatted(digest)))
+ wireMock.register(head(urlEqualTo(String.format("/v2/library/redirect-blob/blobs/%s", digest)))
.willReturn(aResponse().withStatus(200)));
Registry registry = Registry.Builder.builder()
@@ -825,7 +807,7 @@ void shouldFollowRedirectOnBlobFetch(WireMockRuntimeInfo wmRuntimeInfo) {
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/redirect-blob".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/redirect-blob", registryUrl));
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
assertEquals("blob-data", new String(blob));
}
@@ -845,7 +827,7 @@ void shouldComputeSizeWhenGettingDescriptorIfNull(WireMockRuntimeInfo wmRuntimeI
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/null-size".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/null-size", registryUrl));
Descriptor descriptor = registry.getDescriptor(containerRef);
assertNotNull(descriptor, "Descriptor should not be null");
@@ -870,7 +852,7 @@ void shouldGetSizeFromHeaderWhenGettingDescriptor(WireMockRuntimeInfo wmRuntimeI
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/header-size-size".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/header-size-size", registryUrl));
Descriptor descriptor = registry.getDescriptor(containerRef);
assertNotNull(descriptor, "Descriptor should not be null");
@@ -882,7 +864,7 @@ void shouldValidateDockerContentDigestForUnknownAlgorithm(WireMockRuntimeInfo wm
WireMock wireMock = wmRuntimeInfo.getWireMock();
String registryUrl = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
- wireMock.register(any(urlEqualTo("/v2/library/validate-digest/blobs/%s".formatted(digest)))
+ wireMock.register(any(urlEqualTo(String.format("/v2/library/validate-digest/blobs/%s", digest)))
.willReturn(aResponse()
.withStatus(200)
.withBody("blob-data")
@@ -893,7 +875,7 @@ void shouldValidateDockerContentDigestForUnknownAlgorithm(WireMockRuntimeInfo wm
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/validate-digest".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/validate-digest", registryUrl));
OrasException e = assertThrows(
OrasException.class,
() -> registry.getBlob(containerRef.withDigest(digest)),
@@ -906,7 +888,7 @@ void shouldValidateDockerContentDigestMismatch(WireMockRuntimeInfo wmRuntimeInfo
WireMock wireMock = wmRuntimeInfo.getWireMock();
String registryUrl = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
- wireMock.register(any(urlEqualTo("/v2/library/validate-digest/blobs/%s".formatted(digest)))
+ wireMock.register(any(urlEqualTo(String.format("/v2/library/validate-digest/blobs/%s", digest)))
.willReturn(aResponse()
.withStatus(200)
.withBody("blob-data")
@@ -917,7 +899,7 @@ void shouldValidateDockerContentDigestMismatch(WireMockRuntimeInfo wmRuntimeInfo
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/validate-digest".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/validate-digest", registryUrl));
OrasException e = assertThrows(
OrasException.class,
() -> registry.getBlob(containerRef.withDigest(digest)),
@@ -939,7 +921,7 @@ void shouldNotValidateDockerContentDigestWhenProbingDescriptor(WireMockRuntimeIn
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/validate-digest".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/validate-digest", registryUrl));
Descriptor descriptor = registry.probeDescriptor(containerRef);
assertNotNull(descriptor, "Descriptor should not be null");
}
@@ -952,19 +934,21 @@ void shouldFollowRedirectAfterRequestingToken(WireMockRuntimeInfo wmRuntimeInfo)
String registryUrl = wmRuntimeInfo.getHttpBaseUrl().replace("http://", "");
// Redirect to a fake other storage
- String redirectUrl = "http://%s/storage/%s".formatted(registryUrl, digest);
+ String redirectUrl = String.format("http://%s/storage/%s", registryUrl, digest);
WireMock wireMock = wmRuntimeInfo.getWireMock();
// First we need to authenticate
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/get-first-token/blobs/%s".formatted(digest)))
- .inScenario("redirect after token")
- .willSetStateTo("auth requested")
- .willReturn(WireMock.unauthorized()
- .withHeader(
- Const.WWW_AUTHENTICATE_HEADER,
- "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/get-first-token:pull\""
- .formatted(wmRuntimeInfo.getHttpPort()))));
+ wireMock.register(
+ WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/get-first-token/blobs/%s", digest)))
+ .inScenario("redirect after token")
+ .willSetStateTo("auth requested")
+ .willReturn(WireMock.unauthorized()
+ .withHeader(
+ Const.WWW_AUTHENTICATE_HEADER,
+ String.format(
+ "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/get-first-token:pull\"",
+ wmRuntimeInfo.getHttpPort()))));
// Token is returned
wireMock.register(WireMock.any(
@@ -976,14 +960,15 @@ void shouldFollowRedirectAfterRequestingToken(WireMockRuntimeInfo wmRuntimeInfo)
new HttpClient.TokenResponse("fake-token", "access-token", null, 300, ZonedDateTime.now())))));
// After getting token we get a redirect
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/get-first-token/blobs/%s".formatted(digest)))
- .inScenario("redirect after token")
- .whenScenarioStateIs("got token")
- .willSetStateTo("redirect")
- .willReturn(WireMock.temporaryRedirect(redirectUrl)));
+ wireMock.register(
+ WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/get-first-token/blobs/%s", digest)))
+ .inScenario("redirect after token")
+ .whenScenarioStateIs("got token")
+ .willSetStateTo("redirect")
+ .willReturn(WireMock.temporaryRedirect(redirectUrl)));
// We finally get the blob
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/storage/%s".formatted(digest)))
+ wireMock.register(WireMock.any(WireMock.urlEqualTo(String.format("/storage/%s", digest)))
.inScenario("redirect after token")
.whenScenarioStateIs("redirect")
.willSetStateTo("done")
@@ -995,7 +980,7 @@ void shouldFollowRedirectAfterRequestingToken(WireMockRuntimeInfo wmRuntimeInfo)
.withInsecure(true)
.build();
ContainerRef containerRef =
- ContainerRef.parse("localhost:%d/library/get-first-token".formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef.parse(String.format("localhost:%d/library/get-first-token", wmRuntimeInfo.getHttpPort()));
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
assertEquals("blob-data", new String(blob));
}
@@ -1019,7 +1004,8 @@ void shouldHandleConcurrentBlobPushes(WireMockRuntimeInfo wmRuntimeInfo) throws
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/concurrent-blob@%s".formatted(registryUrl, digest));
+ ContainerRef containerRef =
+ ContainerRef.parse(String.format("%s/library/concurrent-blob@%s", registryUrl, digest));
// Create a temporary file for pushing
Path testFile = configDir.resolve("concurrent-data.temp");
@@ -1056,7 +1042,7 @@ void shouldHandleNetworkConnectivityLoss(WireMockRuntimeInfo wmRuntimeInfo) {
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/network-loss".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/network-loss", registryUrl));
// Verify that a network connectivity loss results in an OrasException
OrasException exception = assertThrows(OrasException.class, () -> registry.getTags(ref));
@@ -1070,9 +1056,9 @@ void shouldHandleCorruptedResponse(WireMockRuntimeInfo wmRuntimeInfo) {
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
// Setup WireMock to return a corrupted blob response
- wireMock.register(head(urlEqualTo("/v2/library/corrupted-blob/blobs/%s".formatted(digest)))
+ wireMock.register(head(urlEqualTo(String.format("/v2/library/corrupted-blob/blobs/%s", digest)))
.willReturn(aResponse().withStatus(200)));
- wireMock.register(get(urlEqualTo("/v2/library/corrupted-blob/blobs/%s".formatted(digest)))
+ wireMock.register(get(urlEqualTo(String.format("/v2/library/corrupted-blob/blobs/%s", digest)))
.willReturn(aResponse()
.withStatus(200)
.withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digest)
@@ -1083,7 +1069,7 @@ void shouldHandleCorruptedResponse(WireMockRuntimeInfo wmRuntimeInfo) {
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/corrupted-blob".formatted(registryUrl));
+ ContainerRef containerRef = ContainerRef.parse(String.format("%s/library/corrupted-blob", registryUrl));
// Expect digest mismatch exception
OrasException exception =
@@ -1099,24 +1085,27 @@ private byte[] tokenScenario(
// Return data from wiremock
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/%s/blobs/%s".formatted(registryName, digest)))
- .inScenario(registryName)
- .willReturn(WireMock.unauthorized()
- .withHeader(
- Const.WWW_AUTHENTICATE_HEADER,
- "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/%s:pull\""
- .formatted(wmRuntimeInfo.getHttpPort(), registryName))));
+ wireMock.register(
+ WireMock.any(WireMock.urlEqualTo(String.format("/v2/library/%s/blobs/%s", registryName, digest)))
+ .inScenario(registryName)
+ .willReturn(WireMock.unauthorized()
+ .withHeader(
+ Const.WWW_AUTHENTICATE_HEADER,
+ String.format(
+ "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/%s:pull\"",
+ wmRuntimeInfo.getHttpPort(), registryName))));
// Return token
wireMock.register(WireMock.any(WireMock.urlEqualTo(
- "/token?scope=repository:library/%s:pull&service=localhost".formatted(registryName)))
+ String.format("/token?scope=repository:library/%s:pull&service=localhost", registryName)))
.inScenario(registryName)
.willSetStateTo("get")
.willReturn(WireMock.okJson(JsonUtils.toJson(
new HttpClient.TokenResponse(token, accessToken, null, 300, ZonedDateTime.now())))));
// On the second call we return ok
- wireMock.register(WireMock.any(WireMock.urlEqualTo("/v2/library/%s/blobs/%s".formatted(registryName, digest)))
+ wireMock.register(WireMock.any(
+ WireMock.urlEqualTo(String.format("/v2/library/%s/blobs/%s", registryName, digest)))
.inScenario(registryName)
.whenScenarioStateIs("get")
.willReturn(
@@ -1129,7 +1118,7 @@ private byte[] tokenScenario(
.build();
ContainerRef containerRef =
- ContainerRef.parse("localhost:%d/library/%s".formatted(wmRuntimeInfo.getHttpPort(), registryName));
+ ContainerRef.parse(String.format("localhost:%d/library/%s", wmRuntimeInfo.getHttpPort(), registryName));
return registry.getBlob(containerRef.withDigest(digest));
}
@@ -1166,7 +1155,7 @@ void pullArtifactShouldRejectInvalidTitleAnnotation(WireMockRuntimeInfo wmRuntim
.withBody(manifestJson)));
// Stub GET blob
- wireMock.register(get(urlEqualTo("/v2/library/malicious-artifact/blobs/%s".formatted(blobDigest)))
+ wireMock.register(get(urlEqualTo(String.format("/v2/library/malicious-artifact/blobs/%s", blobDigest)))
.willReturn(aResponse()
.withStatus(200)
.withBody(blobContent)
@@ -1177,7 +1166,8 @@ void pullArtifactShouldRejectInvalidTitleAnnotation(WireMockRuntimeInfo wmRuntim
.withInsecure(true)
.build();
- ContainerRef containerRef = ContainerRef.parse("%s/library/malicious-artifact:latest".formatted(registryUrl));
+ ContainerRef containerRef =
+ ContainerRef.parse(String.format("%s/library/malicious-artifact:latest", registryUrl));
Path outputDir = configDir.resolve("pull-output");
Files.createDirectories(outputDir);
@@ -1232,7 +1222,7 @@ void shouldRetryOn429WithRetryAfterHeader(WireMockRuntimeInfo wmRuntimeInfo) {
.withMaxRetries(2)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/rate-limited-retry".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/rate-limited-retry", registryUrl));
Tags tags = registry.getTags(ref);
assertEquals(List.of("latest"), tags.tags());
@@ -1269,7 +1259,7 @@ void shouldRetryOn429WithExponentialBackoff(WireMockRuntimeInfo wmRuntimeInfo) {
.withMaxRetries(3)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/rate-limited-backoff".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/rate-limited-backoff", registryUrl));
Tags tags = registry.getTags(ref);
assertEquals(List.of("v1"), tags.tags());
@@ -1299,7 +1289,7 @@ void shouldRetryOnNetworkError(WireMockRuntimeInfo wmRuntimeInfo) {
.withMaxRetries(2)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/network-error-retry".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/network-error-retry", registryUrl));
Tags tags = registry.getTags(ref);
assertEquals(List.of("latest"), tags.tags());
@@ -1320,7 +1310,7 @@ void shouldNotRetryOnClientError(WireMockRuntimeInfo wmRuntimeInfo) {
.withMaxRetries(3)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/not-found".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/not-found", registryUrl));
OrasException exception = assertThrows(OrasException.class, () -> registry.getTags(ref));
assertEquals(404, exception.getStatusCode());
@@ -1343,7 +1333,7 @@ void shouldExhaustRetriesAndThrow(WireMockRuntimeInfo wmRuntimeInfo) {
.withMaxRetries(3)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/always-fails".formatted(registryUrl));
+ ContainerRef ref = ContainerRef.parse(String.format("%s/library/always-fails", registryUrl));
assertThrows(OrasException.class, () -> registry.getTags(ref));
// 3 attempts total (initial + 2 retries)
@@ -1357,12 +1347,13 @@ void shouldNotRetryTokenRefreshRequest(WireMockRuntimeInfo wmRuntimeInfo) {
String digest = SupportedAlgorithm.SHA256.digest("blob-data".getBytes());
// Registry returns 401 to trigger token refresh
- wireMock.register(get(urlPathEqualTo("/v2/library/no-retry-token/blobs/%s".formatted(digest)))
+ wireMock.register(get(urlPathEqualTo(String.format("/v2/library/no-retry-token/blobs/%s", digest)))
.willReturn(forbidden()
.withHeader(
Const.WWW_AUTHENTICATE_HEADER,
- "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/no-retry-token:pull\""
- .formatted(wmRuntimeInfo.getHttpPort()))));
+ String.format(
+ "Bearer realm=\"http://localhost:%d/token\",service=\"localhost\",scope=\"repository:library/no-retry-token:pull\"",
+ wmRuntimeInfo.getHttpPort()))));
// Token endpoint always fails with 500
wireMock.register(get(urlPathEqualTo("/token"))
@@ -1375,7 +1366,7 @@ void shouldNotRetryTokenRefreshRequest(WireMockRuntimeInfo wmRuntimeInfo) {
.build();
ContainerRef ref =
- ContainerRef.parse("localhost:%d/library/no-retry-token".formatted(wmRuntimeInfo.getHttpPort()));
+ ContainerRef.parse(String.format("localhost:%d/library/no-retry-token", wmRuntimeInfo.getHttpPort()));
assertThrows(OrasException.class, () -> registry.getBlob(ref.withDigest(digest)));
// Token endpoint must be called exactly once — no retry on token refresh
@@ -1406,7 +1397,7 @@ void shouldFailChunkedUploadWhenInitiationReturnsNon202(WireMockRuntimeInfo wmRu
// Path overload
Path blobFile = configDir.resolve("chunked-init-error.txt");
Files.write(blobFile, content);
- ContainerRef refPath = ContainerRef.parse("%s/library/chunked-init-error".formatted(registryUrl));
+ ContainerRef refPath = ContainerRef.parse(String.format("%s/library/chunked-init-error", registryUrl));
OrasException exPath = assertThrows(OrasException.class, () -> registry.pushBlobChunked(refPath, blobFile, 4L));
assertEquals(
@@ -1415,7 +1406,7 @@ void shouldFailChunkedUploadWhenInitiationReturnsNon202(WireMockRuntimeInfo wmRu
"Exception message should include the unexpected status code");
// InputStream overload
- ContainerRef refStream = ContainerRef.parse("%s/library/chunked-init-error".formatted(registryUrl))
+ ContainerRef refStream = ContainerRef.parse(String.format("%s/library/chunked-init-error", registryUrl))
.withDigest(digest);
OrasException exStream = assertThrows(
@@ -1453,9 +1444,10 @@ void shouldFailCopyWhenIndexNestingExceedsMaxDepth(WireMockRuntimeInfo wmRuntime
.withInsecure(true)
.build();
- ContainerRef source = ContainerRef.parse("localhost:%d/%s:chain".formatted(wmRuntimeInfo.getHttpPort(), repo));
+ ContainerRef source =
+ ContainerRef.parse(String.format("localhost:%d/%s:chain", wmRuntimeInfo.getHttpPort(), repo));
ContainerRef target = ContainerRef.parse(
- "localhost:%d/library/deep-chain-target:copy".formatted(wmRuntimeInfo.getHttpPort()));
+ String.format("localhost:%d/library/deep-chain-target:copy", wmRuntimeInfo.getHttpPort()));
OrasException e = assertThrows(
OrasException.class,
@@ -1476,27 +1468,22 @@ void shouldCopyWithoutLoopingOnCyclicIndexGraph(WireMockRuntimeInfo wmRuntimeInf
// Reference itself
stubNestedSourceIndex(wireMock, srcRepo, "self", digestA, digestA);
- wireMock.register(WireMock.head(WireMock.urlEqualTo("/v2/%s/manifests/%s".formatted(srcRepo, digestA)))
+ wireMock.register(WireMock.head(WireMock.urlEqualTo(String.format("/v2/%s/manifests/%s", srcRepo, digestA)))
.willReturn(WireMock.ok()
.withHeader(Const.CONTENT_TYPE_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE)
.withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digestA)));
// Generic target: accept any manifest push and return an empty index on the follow-up read.
- String emptyIndex =
- """
- {
- "schemaVersion": 2,
- "mediaType": "%s",
- "manifests": []
- }"""
- .formatted(Const.DEFAULT_INDEX_MEDIA_TYPE);
- wireMock.register(WireMock.put(WireMock.urlMatching("/v2/%s/manifests/.*".formatted(dstRepo)))
+ String emptyIndex = String.format(
+ "{\n" + " \"schemaVersion\": 2,\n" + " \"mediaType\": \"%s\",\n" + " \"manifests\": []\n" + "}",
+ Const.DEFAULT_INDEX_MEDIA_TYPE);
+ wireMock.register(WireMock.put(WireMock.urlMatching(String.format("/v2/%s/manifests/.*", dstRepo)))
.willReturn(WireMock.created().withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digestA)));
- wireMock.register(WireMock.head(WireMock.urlMatching("/v2/%s/manifests/.*".formatted(dstRepo)))
+ wireMock.register(WireMock.head(WireMock.urlMatching(String.format("/v2/%s/manifests/.*", dstRepo)))
.willReturn(WireMock.ok()
.withHeader(Const.CONTENT_TYPE_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE)
.withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digestA)));
- wireMock.register(WireMock.get(WireMock.urlMatching("/v2/%s/manifests/.*".formatted(dstRepo)))
+ wireMock.register(WireMock.get(WireMock.urlMatching(String.format("/v2/%s/manifests/.*", dstRepo)))
.willReturn(WireMock.ok()
.withHeader(Const.CONTENT_TYPE_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE)
.withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digestA)
@@ -1508,37 +1495,38 @@ void shouldCopyWithoutLoopingOnCyclicIndexGraph(WireMockRuntimeInfo wmRuntimeInf
.build();
ContainerRef source =
- ContainerRef.parse("localhost:%d/%s:self".formatted(wmRuntimeInfo.getHttpPort(), srcRepo));
+ ContainerRef.parse(String.format("localhost:%d/%s:self", wmRuntimeInfo.getHttpPort(), srcRepo));
ContainerRef target =
- ContainerRef.parse("localhost:%d/%s:copy".formatted(wmRuntimeInfo.getHttpPort(), dstRepo));
+ ContainerRef.parse(String.format("localhost:%d/%s:copy", wmRuntimeInfo.getHttpPort(), dstRepo));
// Terminates rather than looping / overflowing.
CopyUtils.copy(registry, source, registry, target, CopyUtils.CopyOptions.deep());
// The cycle guard returned before getIndex
wireMock.verifyThat(
- 0, WireMock.getRequestedFor(WireMock.urlEqualTo("/v2/%s/manifests/%s".formatted(srcRepo, digestA))));
+ 0,
+ WireMock.getRequestedFor(WireMock.urlEqualTo(String.format("/v2/%s/manifests/%s", srcRepo, digestA))));
wireMock.verifyThat(
- WireMock.headRequestedFor(WireMock.urlEqualTo("/v2/%s/manifests/%s".formatted(srcRepo, digestA))));
+ WireMock.headRequestedFor(WireMock.urlEqualTo(String.format("/v2/%s/manifests/%s", srcRepo, digestA))));
}
/**
* Build the JSON of an index whose single entry is another index (the child).
*/
private static String nestedIndexJson(String childDigest) {
- return """
- {
- "schemaVersion": 2,
- "mediaType": "%s",
- "manifests": [
- {
- "mediaType": "%s",
- "digest": "%s",
- "size": 100
- }
- ]
- }"""
- .formatted(Const.DEFAULT_INDEX_MEDIA_TYPE, Const.DEFAULT_INDEX_MEDIA_TYPE, childDigest);
+ return String.format(
+ "{\n"
+ + " \"schemaVersion\": 2,\n"
+ + " \"mediaType\": \"%s\",\n"
+ + " \"manifests\": [\n"
+ + " {\n"
+ + " \"mediaType\": \"%s\",\n"
+ + " \"digest\": \"%s\",\n"
+ + " \"size\": 100\n"
+ + " }\n"
+ + " ]\n"
+ + "}",
+ Const.DEFAULT_INDEX_MEDIA_TYPE, Const.DEFAULT_INDEX_MEDIA_TYPE, childDigest);
}
/**
@@ -1546,7 +1534,7 @@ private static String nestedIndexJson(String childDigest) {
*/
private static void stubNestedSourceIndex(
WireMock wireMock, String repo, String ref, String selfDigest, String childDigest) {
- String url = "/v2/%s/manifests/%s".formatted(repo, ref);
+ String url = String.format("/v2/%s/manifests/%s", repo, ref);
wireMock.register(WireMock.head(WireMock.urlEqualTo(url))
.willReturn(WireMock.ok()
.withHeader(Const.CONTENT_TYPE_HEADER, Const.DEFAULT_INDEX_MEDIA_TYPE)
@@ -1565,16 +1553,18 @@ void shouldRejectBlobWhenContentDoesNotMatchPinnedDigest(WireMockRuntimeInfo wmR
String selfConsistentHeader = SupportedAlgorithm.SHA256.digest(evil.getBytes(StandardCharsets.UTF_8));
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/evil-blob/blobs/%s".formatted(pinnedDigest)))
- .willReturn(WireMock.ok()
- .withBody(evil)
- .withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, selfConsistentHeader)));
+ wireMock.register(
+ WireMock.get(WireMock.urlEqualTo(String.format("/v2/library/evil-blob/blobs/%s", pinnedDigest)))
+ .willReturn(WireMock.ok()
+ .withBody(evil)
+ .withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, selfConsistentHeader)));
Registry registry = Registry.Builder.builder()
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("localhost:%d/library/evil-blob".formatted(wmRuntimeInfo.getHttpPort()))
+ ContainerRef ref = ContainerRef.parse(
+ String.format("localhost:%d/library/evil-blob", wmRuntimeInfo.getHttpPort()))
.withDigest(pinnedDigest);
OrasException ex = assertThrows(OrasException.class, () -> registry.getBlob(ref));
@@ -1587,14 +1577,15 @@ void shouldReturnBinaryBlobWithoutCorruption(WireMockRuntimeInfo wmRuntimeInfo)
String digest = SupportedAlgorithm.SHA256.digest(binary);
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/bin-blob/blobs/%s".formatted(digest)))
+ wireMock.register(WireMock.get(WireMock.urlEqualTo(String.format("/v2/library/bin-blob/blobs/%s", digest)))
.willReturn(WireMock.ok().withBody(binary).withHeader(Const.DOCKER_CONTENT_DIGEST_HEADER, digest)));
Registry registry = Registry.Builder.builder()
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("localhost:%d/library/bin-blob".formatted(wmRuntimeInfo.getHttpPort()))
+ ContainerRef ref = ContainerRef.parse(
+ String.format("localhost:%d/library/bin-blob", wmRuntimeInfo.getHttpPort()))
.withDigest(digest);
assertArrayEquals(binary, registry.getBlob(ref));
@@ -1605,14 +1596,16 @@ void shouldRejectFetchBlobToPathOnDigestMismatch(WireMockRuntimeInfo wmRuntimeIn
String pinnedDigest = SupportedAlgorithm.SHA256.digest("good".getBytes(StandardCharsets.UTF_8));
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/evil-file/blobs/%s".formatted(pinnedDigest)))
- .willReturn(WireMock.ok().withBody("tampered")));
+ wireMock.register(
+ WireMock.get(WireMock.urlEqualTo(String.format("/v2/library/evil-file/blobs/%s", pinnedDigest)))
+ .willReturn(WireMock.ok().withBody("tampered")));
Registry registry = Registry.Builder.builder()
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("localhost:%d/library/evil-file".formatted(wmRuntimeInfo.getHttpPort()))
+ ContainerRef ref = ContainerRef.parse(
+ String.format("localhost:%d/library/evil-file", wmRuntimeInfo.getHttpPort()))
.withDigest(pinnedDigest);
Path out = configDir.resolve("blob-out.bin");
@@ -1625,14 +1618,16 @@ void shouldRejectBlobStreamOnDigestMismatch(WireMockRuntimeInfo wmRuntimeInfo) {
String pinnedDigest = SupportedAlgorithm.SHA256.digest("good".getBytes(StandardCharsets.UTF_8));
WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/evil-stream/blobs/%s".formatted(pinnedDigest)))
- .willReturn(WireMock.ok().withBody("tampered")));
+ wireMock.register(
+ WireMock.get(WireMock.urlEqualTo(String.format("/v2/library/evil-stream/blobs/%s", pinnedDigest)))
+ .willReturn(WireMock.ok().withBody("tampered")));
Registry registry = Registry.Builder.builder()
.withAuthProvider(authProvider)
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("localhost:%d/library/evil-stream".formatted(wmRuntimeInfo.getHttpPort()))
+ ContainerRef ref = ContainerRef.parse(
+ String.format("localhost:%d/library/evil-stream", wmRuntimeInfo.getHttpPort()))
.withDigest(pinnedDigest);
// The digest is verified incrementally, so the mismatch surfaces when the stream is read to EOF.
@@ -1647,17 +1642,13 @@ void shouldRejectBlobStreamOnDigestMismatch(WireMockRuntimeInfo wmRuntimeInfo) {
@Test
void shouldRejectManifestWhoseContentDoesNotMatchPinnedDigest(WireMockRuntimeInfo wmRuntimeInfo) {
String realManifest =
- """
- {"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "config":{"mediaType":"application/vnd.oci.empty.v1+json",\
- "digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},\
- "layers":[]}""";
+ "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"config\":{\"mediaType\":\"application/vnd.oci.empty.v1+json\",\"digest\":\"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a\",\"size\":2},\"layers\":[]}";
String evilManifest = realManifest.replace("\"layers\":[]", "\"layers\":[],\"annotations\":{\"x\":\"y\"}");
String pinnedDigest = SupportedAlgorithm.SHA256.digest(realManifest.getBytes(StandardCharsets.UTF_8));
String selfConsistentHeader = SupportedAlgorithm.SHA256.digest(evilManifest.getBytes(StandardCharsets.UTF_8));
WireMock wireMock = wmRuntimeInfo.getWireMock();
- String manifestPath = "/v2/library/evil-manifest/manifests/%s".formatted(pinnedDigest);
+ String manifestPath = String.format("/v2/library/evil-manifest/manifests/%s", pinnedDigest);
wireMock.register(WireMock.head(WireMock.urlEqualTo(manifestPath))
.willReturn(WireMock.aResponse()
.withStatus(200)
@@ -1675,7 +1666,7 @@ void shouldRejectManifestWhoseContentDoesNotMatchPinnedDigest(WireMockRuntimeInf
.withInsecure(true)
.build();
ContainerRef ref = ContainerRef.parse(
- "localhost:%d/library/evil-manifest".formatted(wmRuntimeInfo.getHttpPort()))
+ String.format("localhost:%d/library/evil-manifest", wmRuntimeInfo.getHttpPort()))
.withDigest(pinnedDigest);
OrasException ex = assertThrows(OrasException.class, () -> registry.getManifest(ref));
@@ -1709,8 +1700,9 @@ void shouldFailOnTagListPaginationExceedingMaxPages(WireMockRuntimeInfo wmRuntim
OrasException exception = assertThrows(
OrasException.class,
- () -> registry.getTags(ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))));
+ () -> registry.getTags(ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))));
assertTrue(
exception.getMessage().contains("Tag listing exceeded 2 pages"),
"Unexpected message: " + exception.getMessage());
@@ -1724,15 +1716,9 @@ void shouldFailOnReferrerListPaginationExceedingMaxPages(WireMockRuntimeInfo wmR
// Minimal referrers index JSON with one manifest entry per page
String page1 =
- """
- {"mediaType":"application/vnd.oci.image.index.v1+json","manifests":\
- [{"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","size":1}]}""";
+ "{\"mediaType\":\"application/vnd.oci.image.index.v1+json\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"size\":1}]}";
String page2 =
- """
- {"mediaType":"application/vnd.oci.image.index.v1+json","manifests":\
- [{"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","size":1}]}""";
+ "{\"mediaType\":\"application/vnd.oci.image.index.v1+json\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1}]}";
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(WireMock.get(WireMock.urlPathEqualTo(referrersPath))
@@ -1756,8 +1742,9 @@ void shouldFailOnReferrerListPaginationExceedingMaxPages(WireMockRuntimeInfo wmR
.withReferrerListMaxPages(2)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
+ ContainerRef ref = ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
.withDigest(digest);
OrasException exception = assertThrows(OrasException.class, () -> registry.getReferrers(ref, null));
@@ -1775,11 +1762,7 @@ void shouldFallbackToReferrersTagSchemaWhenReferrersApiUnavailable(WireMockRunti
+ "sha256-44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a";
String fallbackIndex =
- """
- {"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":\
- [{"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","size":1,\
- "artifactType":"application/vnd.example+json"}]}""";
+ "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1+json\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"size\":1,\"artifactType\":\"application/vnd.example+json\"}]}";
WireMock wireMock = wmRuntimeInfo.getWireMock();
// Registry does not implement the Referrers API (e.g. GHCR)
@@ -1796,8 +1779,9 @@ void shouldFallbackToReferrersTagSchemaWhenReferrersApiUnavailable(WireMockRunti
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
+ ContainerRef ref = ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
.withDigest(digest);
Referrers referrers = registry.getReferrers(ref, null);
@@ -1816,14 +1800,7 @@ void shouldFilterFallbackReferrersByArtifactType(WireMockRuntimeInfo wmRuntimeIn
+ "sha256-44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a";
String fallbackIndex =
- """
- {"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":\
- [{"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","size":1,\
- "artifactType":"application/vnd.example+json"},\
- {"mediaType":"application/vnd.oci.image.manifest.v1+json",\
- "digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","size":1,\
- "artifactType":"application/vnd.other+json"}]}""";
+ "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.index.v1+json\",\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"size\":1,\"artifactType\":\"application/vnd.example+json\"},{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1,\"artifactType\":\"application/vnd.other+json\"}]}";
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(WireMock.get(WireMock.urlPathEqualTo(referrersPath)).willReturn(WireMock.notFound()));
@@ -1838,8 +1815,9 @@ void shouldFilterFallbackReferrersByArtifactType(WireMockRuntimeInfo wmRuntimeIn
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
+ ContainerRef ref = ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
.withDigest(digest);
Referrers referrers = registry.getReferrers(ref, ArtifactType.from("application/vnd.example+json"));
@@ -1866,8 +1844,9 @@ void shouldReturnEmptyReferrersWhenFallbackTagDoesNotExist(WireMockRuntimeInfo w
.withInsecure(true)
.build();
- ContainerRef ref = ContainerRef.parse("%s/library/artifact-text"
- .formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
+ ContainerRef ref = ContainerRef.parse(String.format(
+ "%s/library/artifact-text",
+ wmRuntimeInfo.getHttpBaseUrl().replace("http://", "")))
.withDigest(digest);
Referrers referrers = registry.getReferrers(ref, null);
diff --git a/src/test/java/land/oras/SubjectTest.java b/src/test/java/land/oras/SubjectTest.java
index 9efc8183..c2aa19f3 100644
--- a/src/test/java/land/oras/SubjectTest.java
+++ b/src/test/java/land/oras/SubjectTest.java
@@ -70,12 +70,10 @@ void testToString() {
*/
private String sampleSubject() {
// language=JSON
- return """
- {
- "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
- "digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "size": 32654
- }
- """;
+ return "{\n"
+ + " \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n"
+ + " \"digest\": \"sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n"
+ + " \"size\": 32654\n"
+ + "}\n";
}
}
diff --git a/src/test/java/land/oras/auth/AuthStoreAuthenticationProviderTest.java b/src/test/java/land/oras/auth/AuthStoreAuthenticationProviderTest.java
index dfde8673..439be68b 100644
--- a/src/test/java/land/oras/auth/AuthStoreAuthenticationProviderTest.java
+++ b/src/test/java/land/oras/auth/AuthStoreAuthenticationProviderTest.java
@@ -55,7 +55,7 @@ void testGetAuthHeader() throws Exception {
AuthStoreAuthenticationProvider authProvider = new AuthStoreAuthenticationProvider(mockAuthStore);
// Verify that the getAuthHeader method returns the expected Basic Auth header
- String authHeader = authProvider.getAuthHeader(ContainerRef.parse("%s/%s".formatted(REGISTRY, "alpine")));
+ String authHeader = authProvider.getAuthHeader(ContainerRef.parse(String.format("%s/%s", REGISTRY, "alpine")));
String expectedAuthString = "testUser:testPassword";
String expectedEncodedAuth =
"Basic " + Base64.getEncoder().encodeToString(expectedAuthString.getBytes(StandardCharsets.UTF_8));
@@ -73,7 +73,7 @@ void testGetNullAuthHeader() throws Exception {
AuthStoreAuthenticationProvider authProvider = new AuthStoreAuthenticationProvider(mockAuthStore);
// Verify that the getAuthHeader method returns the expected Basic Auth header
- String authHeader = authProvider.getAuthHeader(ContainerRef.parse("%s/%s".formatted(REGISTRY, "alpine")));
+ String authHeader = authProvider.getAuthHeader(ContainerRef.parse(String.format("%s/%s", REGISTRY, "alpine")));
String expectedAuthString = "testUser:testPassword";
String expectedEncodedAuth =
"Basic " + Base64.getEncoder().encodeToString(expectedAuthString.getBytes(StandardCharsets.UTF_8));
@@ -88,8 +88,8 @@ void testDefaultLocation() {
@Test
void identityShouldFollowTheResolvedCredentialPerRegistry() {
- ContainerRef alpine = ContainerRef.parse("%s/%s".formatted(REGISTRY, "alpine"));
- ContainerRef busybox = ContainerRef.parse("%s/%s".formatted(REGISTRY, "busybox"));
+ ContainerRef alpine = ContainerRef.parse(String.format("%s/%s", REGISTRY, "alpine"));
+ ContainerRef busybox = ContainerRef.parse(String.format("%s/%s", REGISTRY, "busybox"));
doReturn(new Credential("alice", "alice-pass")).when(mockAuthStore).get(alpine);
doReturn(new Credential("bob", "bob-pass")).when(mockAuthStore).get(busybox);
@@ -107,6 +107,7 @@ void identityShouldFallBackToAnonymousWhenNoCredentialIsStored() {
AuthStoreAuthenticationProvider authProvider = new AuthStoreAuthenticationProvider(mockAuthStore);
assertEquals(
- "BASIC:anonymous", authProvider.getIdentity(ContainerRef.parse("%s/%s".formatted(REGISTRY, "alpine"))));
+ "BASIC:anonymous",
+ authProvider.getIdentity(ContainerRef.parse(String.format("%s/%s", REGISTRY, "alpine"))));
}
}
diff --git a/src/test/java/land/oras/auth/AuthStoreTest.java b/src/test/java/land/oras/auth/AuthStoreTest.java
index 4d8fa29f..3b46fcf6 100644
--- a/src/test/java/land/oras/auth/AuthStoreTest.java
+++ b/src/test/java/land/oras/auth/AuthStoreTest.java
@@ -58,52 +58,46 @@ class AuthStoreTest {
private static final String PASSWORD = "password";
// language=json
- public static final String SAMPLE_DOCKER_CONFIG =
- """
- {
- "auths": {
- "registry.example.com": {
- "auth": "dXNlcjpwYXNzd29yZA=="
- },
- "another.registry.com": {
- "auth": "dXNlcjpwYXNzd29yZA=="
- }
- },
- "credsStore": "unknown",
- "credHelpers": {
- "registry.other.com": "foo-binary",
- "another.other.com": "bar-binary",
- "new.other.com": "pass",
- "other.other.com": "other-binary",
- "creds.other.com": "fake",
- "error.other.com": "fake"
- }
- }
- """;
+ public static final String SAMPLE_DOCKER_CONFIG = "{\n"
+ + " \"auths\": {\n"
+ + " \"registry.example.com\": {\n"
+ + " \"auth\": \"dXNlcjpwYXNzd29yZA==\"\n"
+ + " },\n"
+ + " \"another.registry.com\": {\n"
+ + " \"auth\": \"dXNlcjpwYXNzd29yZA==\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"credsStore\": \"unknown\",\n"
+ + " \"credHelpers\": {\n"
+ + " \"registry.other.com\": \"foo-binary\",\n"
+ + " \"another.other.com\": \"bar-binary\",\n"
+ + " \"new.other.com\": \"pass\",\n"
+ + " \"other.other.com\": \"other-binary\",\n"
+ + " \"creds.other.com\": \"fake\",\n"
+ + " \"error.other.com\": \"fake\"\n"
+ + " }\n"
+ + "}\n";
// language=json
- public static final String SAMPLE_PODMAN_CONFIG =
- """
- {
- "auths": {
- "registry.other.com": {
- "auth": "dXNlcjpwYXNzd29yZA=="
- },
- "another.other.com": {
- "auth": "dXNlcjpwYXNzd29yZA=="
- }
- },
- "credsStore": "unknown",
- "credHelpers": {
- "registry.other.com": "foo-binary",
- "another.other.com": "bar-binary",
- "new.other.com": "pass",
- "other.other.com": "other-binary",
- "creds.other.com": "fake",
- "error.other.com": "fake"
- }
- }
- """;
+ public static final String SAMPLE_PODMAN_CONFIG = "{\n"
+ + " \"auths\": {\n"
+ + " \"registry.other.com\": {\n"
+ + " \"auth\": \"dXNlcjpwYXNzd29yZA==\"\n"
+ + " },\n"
+ + " \"another.other.com\": {\n"
+ + " \"auth\": \"dXNlcjpwYXNzd29yZA==\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"credsStore\": \"unknown\",\n"
+ + " \"credHelpers\": {\n"
+ + " \"registry.other.com\": \"foo-binary\",\n"
+ + " \"another.other.com\": \"bar-binary\",\n"
+ + " \"new.other.com\": \"pass\",\n"
+ + " \"other.other.com\": \"other-binary\",\n"
+ + " \"creds.other.com\": \"fake\",\n"
+ + " \"error.other.com\": \"fake\"\n"
+ + " }\n"
+ + "}\n";
@BeforeAll
static void init() throws Exception {
@@ -293,22 +287,19 @@ void testShouldReadCredentialsFromPodManConfig() throws Exception {
}
// language=json
- public static final String SAMPLE_HIERARCHICAL_CONFIG =
- """
- {
- "auths": {
- "my-registry.local/namespace/user/image": {
- "auth": "dXNlcjE6cGFzczE="
- },
- "my-registry.local/namespace": {
- "auth": "dXNlcjM6cGFzczM="
- },
- "my-registry.local": {
- "auth": "dXNlcjI6cGFzczI="
- }
- }
- }
- """;
+ public static final String SAMPLE_HIERARCHICAL_CONFIG = "{\n"
+ + " \"auths\": {\n"
+ + " \"my-registry.local/namespace/user/image\": {\n"
+ + " \"auth\": \"dXNlcjE6cGFzczE=\"\n"
+ + " },\n"
+ + " \"my-registry.local/namespace\": {\n"
+ + " \"auth\": \"dXNlcjM6cGFzczM=\"\n"
+ + " },\n"
+ + " \"my-registry.local\": {\n"
+ + " \"auth\": \"dXNlcjI6cGFzczI=\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
@Test
void testHierarchicalCredentialLookupMostSpecific() throws Exception {
@@ -412,16 +403,13 @@ void testRegistryAuthFileIsUsedWhenSet() throws Exception {
@Test
void testRegistryAuthFileTakesPrecedenceOverDefaults() throws Exception {
// language=json
- String customConfig =
- """
- {
- "auths": {
- "custom.registry.com": {
- "auth": "dXNlcjpwYXNzd29yZA=="
- }
- }
- }
- """;
+ String customConfig = "{\n"
+ + " \"auths\": {\n"
+ + " \"custom.registry.com\": {\n"
+ + " \"auth\": \"dXNlcjpwYXNzd29yZA==\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
Path authFile = tempDir.resolve("custom-auth.json");
Files.writeString(authFile, customConfig);
@@ -489,15 +477,13 @@ void testPasswordContainingColonIsPreserved() throws Exception {
String auth = java.util.Base64.getEncoder()
.encodeToString((user + ":" + password).getBytes(java.nio.charset.StandardCharsets.UTF_8));
// language=json
- String config =
- """
- {
- "auths": {
- "colon.registry.com": { "auth": "%s" }
- }
- }
- """
- .formatted(auth);
+ String config = String.format(
+ "{\n"
+ + " \"auths\": {\n"
+ + " \"colon.registry.com\": { \"auth\": \"%s\" }\n"
+ + " }\n"
+ + "}\n",
+ auth);
Path configFile = tempDir.resolve("colon-config.json");
Files.writeString(configFile, config);
@@ -516,16 +502,14 @@ void testMalformedEntryIsSkippedWithoutDroppingOtherCredentials() throws Excepti
String valid = java.util.Base64.getEncoder()
.encodeToString("user:password".getBytes(java.nio.charset.StandardCharsets.UTF_8));
// language=json
- String config =
- """
- {
- "auths": {
- "bad.registry.com": { "auth": "%s" },
- "good.registry.com": { "auth": "%s" }
- }
- }
- """
- .formatted(malformed, valid);
+ String config = String.format(
+ "{\n"
+ + " \"auths\": {\n"
+ + " \"bad.registry.com\": { \"auth\": \"%s\" },\n"
+ + " \"good.registry.com\": { \"auth\": \"%s\" }\n"
+ + " }\n"
+ + "}\n",
+ malformed, valid);
Path configFile = tempDir.resolve("malformed-config.json");
Files.writeString(configFile, config);
@@ -542,15 +526,13 @@ void testEmptyPasswordIsPreserved() throws Exception {
String auth =
java.util.Base64.getEncoder().encodeToString("user:".getBytes(java.nio.charset.StandardCharsets.UTF_8));
// language=json
- String config =
- """
- {
- "auths": {
- "empty.registry.com": { "auth": "%s" }
- }
- }
- """
- .formatted(auth);
+ String config = String.format(
+ "{\n"
+ + " \"auths\": {\n"
+ + " \"empty.registry.com\": { \"auth\": \"%s\" }\n"
+ + " }\n"
+ + "}\n",
+ auth);
Path configFile = tempDir.resolve("empty-pass-config.json");
Files.writeString(configFile, config);
@@ -569,15 +551,13 @@ void testPasswordWithArbitraryCharactersIsPreserved() throws Exception {
String auth = java.util.Base64.getEncoder()
.encodeToString((user + ":" + password).getBytes(java.nio.charset.StandardCharsets.UTF_8));
// language=json
- String config =
- """
- {
- "auths": {
- "any.registry.com": { "auth": "%s" }
- }
- }
- """
- .formatted(auth);
+ String config = String.format(
+ "{\n"
+ + " \"auths\": {\n"
+ + " \"any.registry.com\": { \"auth\": \"%s\" }\n"
+ + " }\n"
+ + "}\n",
+ auth);
Path configFile = tempDir.resolve("any-char-config.json");
Files.writeString(configFile, config);
@@ -593,14 +573,9 @@ void testPasswordWithArbitraryCharactersIsPreserved() throws Exception {
void shouldRejectCredentialHelperThatEscapesPrefix() throws Exception {
String maliciousSuffix = "../../../../../../tmp/pwn";
// language=json
- String config =
- """
- {
- "auths": {},
- "credHelpers": { "evil.registry.com": "%s" }
- }
- """
- .formatted(maliciousSuffix);
+ String config = String.format(
+ "{\n" + " \"auths\": {},\n" + " \"credHelpers\": { \"evil.registry.com\": \"%s\" }\n" + "}\n",
+ maliciousSuffix);
Path configFile = tempDir.resolve("evil-helper-config.json");
Files.writeString(configFile, config);
@@ -614,13 +589,10 @@ void shouldRejectCredentialHelperThatEscapesPrefix() throws Exception {
@Test
void shouldReturnValidCredentialHelperBinary() throws Exception {
// language=json
- String config =
- """
- {
- "auths": {},
- "credHelpers": { "good.registry.com": "ecr-login" }
- }
- """;
+ String config = "{\n"
+ + " \"auths\": {},\n"
+ + " \"credHelpers\": { \"good.registry.com\": \"ecr-login\" }\n"
+ + "}\n";
Path configFile = tempDir.resolve("good-helper-config.json");
Files.writeString(configFile, config);
diff --git a/src/test/java/land/oras/auth/RegistriesConfTest.java b/src/test/java/land/oras/auth/RegistriesConfTest.java
index e84cb996..f4a2131f 100644
--- a/src/test/java/land/oras/auth/RegistriesConfTest.java
+++ b/src/test/java/land/oras/auth/RegistriesConfTest.java
@@ -41,13 +41,10 @@ class RegistriesConfTest {
private static Path homeDir;
// language=toml
- public static final String HOME_REGISTRIES_CONF =
- """
- unqualified-search-registries = ["docker.io"]
-
- [aliases]
- "alpine"="docker.io/library/alpine"
- """;
+ public static final String HOME_REGISTRIES_CONF = "unqualified-search-registries = [\"docker.io\"]\n"
+ + "\n"
+ + "[aliases]\n"
+ + "\"alpine\"=\"docker.io/library/alpine\"\n";
@BeforeAll
static void init() {
@@ -81,12 +78,7 @@ void shouldLoadDropInConfFiles(@TempDir Path dropInHomeDir) throws Exception {
TestUtils.createRegistriesConfFile(dropInHomeDir, "unqualified-search-registries = [\"docker.io\"]");
// language=toml
TestUtils.createDropInConfFile(
- dropInHomeDir,
- "10-extra.conf",
- """
- [aliases]
- "myapp"="registry.example.com/myapp"
- """);
+ dropInHomeDir, "10-extra.conf", "[aliases]\n" + "\"myapp\"=\"registry.example.com/myapp\"\n");
TestUtils.withHome(dropInHomeDir, () -> {
RegistriesConf conf = RegistriesConf.newConf();
@@ -103,20 +95,10 @@ void shouldLoadDropInConfFilesInAlphaNumericalOrder(@TempDir Path dropInHomeDir)
TestUtils.createRegistriesConfFile(dropInHomeDir, "");
// language=toml
TestUtils.createDropInConfFile(
- dropInHomeDir,
- "01-first.conf",
- """
- [aliases]
- "foo"="registry.first.com/foo"
- """);
+ dropInHomeDir, "01-first.conf", "[aliases]\n" + "\"foo\"=\"registry.first.com/foo\"\n");
// language=toml
TestUtils.createDropInConfFile(
- dropInHomeDir,
- "02-second.conf",
- """
- [aliases]
- "foo"="registry.second.com/foo"
- """);
+ dropInHomeDir, "02-second.conf", "[aliases]\n" + "\"foo\"=\"registry.second.com/foo\"\n");
TestUtils.withHome(dropInHomeDir, () -> {
RegistriesConf conf = RegistriesConf.newConf();
diff --git a/src/test/java/land/oras/auth/RegistryConfTest.java b/src/test/java/land/oras/auth/RegistryConfTest.java
index 12997250..eec1c78b 100644
--- a/src/test/java/land/oras/auth/RegistryConfTest.java
+++ b/src/test/java/land/oras/auth/RegistryConfTest.java
@@ -222,20 +222,17 @@ void shouldRewriteUnqualifiedContainerRef() {
@Test
void shouldParseMirrorsFromToml() {
// language=toml
- String toml =
- """
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
-
- [[registry.mirror]]
- location = "mirror1.example.com"
- insecure = true
-
- [[registry.mirror]]
- location = "mirror2.example.com"
- insecure = false
- """;
+ String toml = "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror1.example.com\"\n"
+ + "insecure = true\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror2.example.com\"\n"
+ + "insecure = false\n";
RegistriesConf conf = RegistriesConf.newConf(List.of(writeTempToml(toml)));
@@ -362,27 +359,24 @@ void shouldRewriteForMirrorWithTrailingSlash() {
@Test
void shouldParsePullFromMirrorFromToml() {
// language=toml
- String toml =
- """
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
-
- [[registry.mirror]]
- location = "mirror-digest.example.com"
- pull-from-mirror = "digest-only"
-
- [[registry.mirror]]
- location = "mirror-tag.example.com"
- pull-from-mirror = "tag-only"
-
- [[registry.mirror]]
- location = "mirror-all.example.com"
- pull-from-mirror = "all"
-
- [[registry.mirror]]
- location = "mirror-default.example.com"
- """;
+ String toml = "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-digest.example.com\"\n"
+ + "pull-from-mirror = \"digest-only\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-tag.example.com\"\n"
+ + "pull-from-mirror = \"tag-only\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-all.example.com\"\n"
+ + "pull-from-mirror = \"all\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-default.example.com\"\n";
RegistriesConf conf = RegistriesConf.newConf(List.of(writeTempToml(toml)));
ContainerRef ref = ContainerRef.parse("docker.io/library/alpine:latest");
@@ -398,23 +392,20 @@ void shouldParsePullFromMirrorFromToml() {
@Test
void shouldFilterMirrorsByPullFromMirrorForTagRef() {
// language=toml
- String toml =
- """
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
-
- [[registry.mirror]]
- location = "mirror-digest.example.com"
- pull-from-mirror = "digest-only"
-
- [[registry.mirror]]
- location = "mirror-tag.example.com"
- pull-from-mirror = "tag-only"
-
- [[registry.mirror]]
- location = "mirror-all.example.com"
- """;
+ String toml = "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-digest.example.com\"\n"
+ + "pull-from-mirror = \"digest-only\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-tag.example.com\"\n"
+ + "pull-from-mirror = \"tag-only\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-all.example.com\"\n";
RegistriesConf conf = RegistriesConf.newConf(List.of(writeTempToml(toml)));
@@ -430,23 +421,20 @@ void shouldFilterMirrorsByPullFromMirrorForTagRef() {
@Test
void shouldFilterMirrorsByPullFromMirrorForDigestRef() {
// language=toml
- String toml =
- """
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
-
- [[registry.mirror]]
- location = "mirror-digest.example.com"
- pull-from-mirror = "digest-only"
-
- [[registry.mirror]]
- location = "mirror-tag.example.com"
- pull-from-mirror = "tag-only"
-
- [[registry.mirror]]
- location = "mirror-all.example.com"
- """;
+ String toml = "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-digest.example.com\"\n"
+ + "pull-from-mirror = \"digest-only\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-tag.example.com\"\n"
+ + "pull-from-mirror = \"tag-only\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-all.example.com\"\n";
RegistriesConf conf = RegistriesConf.newConf(List.of(writeTempToml(toml)));
@@ -463,20 +451,17 @@ void shouldFilterMirrorsByPullFromMirrorForDigestRef() {
@Test
void shouldApplyMirrorByDigestOnly() {
// language=toml
- String toml =
- """
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
- mirror-by-digest-only = true
-
- [[registry.mirror]]
- location = "mirror-a.example.com"
-
- [[registry.mirror]]
- location = "mirror-b.example.com"
- pull-from-mirror = "tag-only"
- """;
+ String toml = "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "mirror-by-digest-only = true\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-a.example.com\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-b.example.com\"\n"
+ + "pull-from-mirror = \"tag-only\"\n";
RegistriesConf conf = RegistriesConf.newConf(List.of(writeTempToml(toml)));
@@ -496,18 +481,15 @@ void shouldApplyMirrorByDigestOnly() {
@Test
void shouldReturnAllMirrorsWhenNoFilterConfigured() {
// language=toml
- String toml =
- """
- [[registry]]
- prefix = "docker.io"
- location = "docker.io"
-
- [[registry.mirror]]
- location = "mirror-a.example.com"
-
- [[registry.mirror]]
- location = "mirror-b.example.com"
- """;
+ String toml = "[[registry]]\n"
+ + "prefix = \"docker.io\"\n"
+ + "location = \"docker.io\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-a.example.com\"\n"
+ + "\n"
+ + "[[registry.mirror]]\n"
+ + "location = \"mirror-b.example.com\"\n";
RegistriesConf conf = RegistriesConf.newConf(List.of(writeTempToml(toml)));
ContainerRef ref = ContainerRef.parse("docker.io/library/alpine:latest");
diff --git a/src/test/java/land/oras/policy/ContainersPolicyTest.java b/src/test/java/land/oras/policy/ContainersPolicyTest.java
index eca0ab4e..001b9605 100644
--- a/src/test/java/land/oras/policy/ContainersPolicyTest.java
+++ b/src/test/java/land/oras/policy/ContainersPolicyTest.java
@@ -164,9 +164,7 @@ void evaluatingSigstoreSignedPassesScopeGate() {
@Test
void loadsUserPolicyFromHome(@TempDir Path homeDir) throws Exception {
// language=json
- String policyJson = """
- {"default": [{"type": "insecureAcceptAnything"}]}
- """;
+ String policyJson = "{\"default\": [{\"type\": \"insecureAcceptAnything\"}]}\n";
writePolicyFile(homeDir, policyJson);
TestUtils.withHome(homeDir, () -> {
@@ -278,10 +276,9 @@ void sigstoreSignedGlobalDefaultApplies(@TempDir Path dir) throws IOException {
// language=json
Files.writeString(
path,
- """
- {"default": [{"type": "sigstoreSigned", "keyData": "%s"}]}
- """
- .formatted(SigstoreTestSupport.keyData(kp.getPublic())));
+ String.format(
+ "{\"default\": [{\"type\": \"sigstoreSigned\", \"keyData\": \"%s\"}]}\n",
+ SigstoreTestSupport.keyData(kp.getPublic())));
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
assertDoesNotThrow(() -> policy.verify(context(
@@ -360,22 +357,20 @@ void sigstoreSignedWithKeyPathsList(@TempDir Path dir) throws IOException {
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/app": [{
- "type": "sigstoreSigned",
- "keyPaths": ["%s", "%s"]
- }]
- }
- }
- }
- """
- .formatted(
- keyFile1.toString().replace("\\", "\\\\"),
- keyFile2.toString().replace("\\", "\\\\")));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/app\": [{\n"
+ + " \"type\": \"sigstoreSigned\",\n"
+ + " \"keyPaths\": [\"%s\", \"%s\"]\n"
+ + " }]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ keyFile1.toString().replace("\\", "\\\\"),
+ keyFile2.toString().replace("\\", "\\\\")));
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
// Accepted when signed by kp1
@@ -409,22 +404,19 @@ void sigstoreSignedWithKeyDatasList(@TempDir Path dir) throws IOException {
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/app": [{
- "type": "sigstoreSigned",
- "keyDatas": ["%s", "%s"]
- }]
- }
- }
- }
- """
- .formatted(
- SigstoreTestSupport.keyData(kp1.getPublic()),
- SigstoreTestSupport.keyData(kp2.getPublic())));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/app\": [{\n"
+ + " \"type\": \"sigstoreSigned\",\n"
+ + " \"keyDatas\": [\"%s\", \"%s\"]\n"
+ + " }]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ SigstoreTestSupport.keyData(kp1.getPublic()), SigstoreTestSupport.keyData(kp2.getPublic())));
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
// Accepted when signed by kp1
@@ -455,19 +447,17 @@ void sigstoreSignedKeyPathsDeserializesCorrectly(@TempDir Path dir) throws IOExc
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/app": [{
- "type": "sigstoreSigned",
- "keyPaths": ["/etc/pki/a.pub", "/etc/pki/b.pub"]
- }]
- }
- }
- }
- """);
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/app\": [{\n"
+ + " \"type\": \"sigstoreSigned\",\n"
+ + " \"keyPaths\": [\"/etc/pki/a.pub\", \"/etc/pki/b.pub\"]\n"
+ + " }]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
List reqs = policy.resolveRequirements(Transport.DOCKER, "registry.example.com/app");
assertEquals(1, reqs.size());
@@ -485,18 +475,16 @@ void signedByGpgIsDeniedBecauseNotImplemented(@TempDir Path dir) throws IOExcept
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/signed": [
- {"type": "signedBy", "keyType": "GPGKeys", "keyPath": "/etc/pki/containers/my-key.gpg"}
- ]
- }
- }
- }
- """);
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/signed\": [\n"
+ + " {\"type\": \"signedBy\", \"keyType\": \"GPGKeys\", \"keyPath\": \"/etc/pki/containers/my-key.gpg\"}\n"
+ + " ]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
assertThrows(
OrasException.class,
@@ -510,19 +498,18 @@ void mixedScopesResolveAndVerifyIndependently(@TempDir Path dir) throws IOExcept
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/open": [{"type": "insecureAcceptAnything"}],
- "registry.example.com/blocked": [{"type": "reject"}],
- "registry.example.com/secure": [{"type": "sigstoreSigned", "keyData": "%s"}]
- }
- }
- }
- """
- .formatted(SigstoreTestSupport.keyData(kp.getPublic())));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/open\": [{\"type\": \"insecureAcceptAnything\"}],\n"
+ + " \"registry.example.com/blocked\": [{\"type\": \"reject\"}],\n"
+ + " \"registry.example.com/secure\": [{\"type\": \"sigstoreSigned\", \"keyData\": \"%s\"}]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ SigstoreTestSupport.keyData(kp.getPublic())));
ContainersPolicy policy = ContainersPolicy.newPolicy(path);
// Open scope: accepted without signatures.
@@ -565,17 +552,16 @@ private static ContainersPolicy sigstorePolicy(Path dir, String scopeKey, String
// language=json
Files.writeString(
path,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "%s": [{"type": "sigstoreSigned", "%s": "%s"}]
- }
- }
- }
- """
- .formatted(scopeKey, keyField, keyValue));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"%s\": [{\"type\": \"sigstoreSigned\", \"%s\": \"%s\"}]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ scopeKey, keyField, keyValue));
return ContainersPolicy.newPolicy(path);
}
diff --git a/src/test/java/land/oras/policy/SigstoreTestSupport.java b/src/test/java/land/oras/policy/SigstoreTestSupport.java
index cbf613ac..b41416dd 100644
--- a/src/test/java/land/oras/policy/SigstoreTestSupport.java
+++ b/src/test/java/land/oras/policy/SigstoreTestSupport.java
@@ -68,12 +68,7 @@ static KeyPair generateKeyPair() {
static String publicKeyPem(PublicKey key) {
String b64 =
Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)).encodeToString(key.getEncoded());
- return """
- -----BEGIN PUBLIC KEY-----
- %s
- -----END PUBLIC KEY-----
- """
- .formatted(b64);
+ return String.format("-----BEGIN PUBLIC KEY-----\n" + "%s\n" + "-----END PUBLIC KEY-----\n", b64);
}
/**
@@ -116,32 +111,31 @@ private static byte[] sign(PrivateKey privateKey, byte[] content) {
private static String inTotoPayload(String imageHex) {
// language=json
- return """
- {
- "_type": "https://in-toto.io/Statement/v1",
- "subject": [{"digest": {"sha256": "%s"}, "annotations": {}}],
- "predicateType": "https://sigstore.dev/cosign/sign/v1",
- "predicate": {}
- }"""
- .formatted(imageHex);
+ return String.format(
+ "{\n"
+ + " \"_type\": \"https://in-toto.io/Statement/v1\",\n"
+ + " \"subject\": [{\"digest\": {\"sha256\": \"%s\"}, \"annotations\": {}}],\n"
+ + " \"predicateType\": \"https://sigstore.dev/cosign/sign/v1\",\n"
+ + " \"predicate\": {}\n"
+ + "}",
+ imageHex);
}
private static String bundleJson(byte[] payload, byte[] signature) {
Base64.Encoder b64 = Base64.getEncoder();
// language=json
- return """
- {
- "mediaType": "%s",
- "dsseEnvelope": {
- "payload": "%s",
- "payloadType": "%s",
- "signatures": [{"sig": "%s"}]
- }
- }"""
- .formatted(
- Const.SIGSTORE_BUNDLE_MEDIA_TYPE,
- b64.encodeToString(payload),
- Const.IN_TOTO_PAYLOAD_TYPE,
- b64.encodeToString(signature));
+ return String.format(
+ "{\n"
+ + " \"mediaType\": \"%s\",\n"
+ + " \"dsseEnvelope\": {\n"
+ + " \"payload\": \"%s\",\n"
+ + " \"payloadType\": \"%s\",\n"
+ + " \"signatures\": [{\"sig\": \"%s\"}]\n"
+ + " }\n"
+ + "}",
+ Const.SIGSTORE_BUNDLE_MEDIA_TYPE,
+ b64.encodeToString(payload),
+ Const.IN_TOTO_PAYLOAD_TYPE,
+ b64.encodeToString(signature));
}
}
diff --git a/src/test/java/land/oras/policy/SigstoreVerifierTest.java b/src/test/java/land/oras/policy/SigstoreVerifierTest.java
index 21cb200b..efbdb2ec 100644
--- a/src/test/java/land/oras/policy/SigstoreVerifierTest.java
+++ b/src/test/java/land/oras/policy/SigstoreVerifierTest.java
@@ -182,19 +182,18 @@ void policyVerificationPassesForSignedImageAndFailsWhenTampered(@TempDir Path di
// language=json
Files.writeString(
policyPath,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/app": [
- {"type": "sigstoreSigned", "keyPath": "%s"}
- ]
- }
- }
- }
- """
- .formatted(keyFile.toString().replace("\\", "\\\\")));
+ String.format(
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/app\": [\n"
+ + " {\"type\": \"sigstoreSigned\", \"keyPath\": \"%s\"}\n"
+ + " ]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n",
+ keyFile.toString().replace("\\", "\\\\")));
ContainersPolicy policy = ContainersPolicy.newPolicy(policyPath);
@@ -226,18 +225,16 @@ void policyVerificationFailsWhenKeyIsMissing(@TempDir Path dir) throws Exception
// language=json
Files.writeString(
policyPath,
- """
- {
- "default": [{"type": "reject"}],
- "transports": {
- "docker": {
- "registry.example.com/app": [
- {"type": "sigstoreSigned", "keyPath": "/nonexistent/cosign.pub"}
- ]
- }
- }
- }
- """);
+ "{\n"
+ + " \"default\": [{\"type\": \"reject\"}],\n"
+ + " \"transports\": {\n"
+ + " \"docker\": {\n"
+ + " \"registry.example.com/app\": [\n"
+ + " {\"type\": \"sigstoreSigned\", \"keyPath\": \"/nonexistent/cosign.pub\"}\n"
+ + " ]\n"
+ + " }\n"
+ + " }\n"
+ + "}\n");
ContainersPolicy policy = ContainersPolicy.newPolicy(policyPath);
PolicyContext context = new PolicyContext(
@@ -271,43 +268,37 @@ private static byte[] sign(PrivateKey priv, byte[] content, String algorithm) th
private static String inTotoPayload(String imageHex) {
// language=json
- return """
- {
- "_type": "https://in-toto.io/Statement/v1",
- "subject": [{"digest": {"sha256": "%s"}, "annotations": {}}],
- "predicateType": "https://sigstore.dev/cosign/sign/v1",
- "predicate": {}
- }"""
- .formatted(imageHex);
+ return String.format(
+ "{\n"
+ + " \"_type\": \"https://in-toto.io/Statement/v1\",\n"
+ + " \"subject\": [{\"digest\": {\"sha256\": \"%s\"}, \"annotations\": {}}],\n"
+ + " \"predicateType\": \"https://sigstore.dev/cosign/sign/v1\",\n"
+ + " \"predicate\": {}\n"
+ + "}",
+ imageHex);
}
private static String bundleJson(byte[] payload, byte[] signature) {
Base64.Encoder b64 = Base64.getEncoder();
// language=json
- return """
- {
- "mediaType": "%s",
- "dsseEnvelope": {
- "payload": "%s",
- "payloadType": "%s",
- "signatures": [{"sig": "%s"}]
- }
- }"""
- .formatted(
- Const.SIGSTORE_BUNDLE_MEDIA_TYPE,
- b64.encodeToString(payload),
- Const.IN_TOTO_PAYLOAD_TYPE,
- b64.encodeToString(signature));
+ return String.format(
+ "{\n"
+ + " \"mediaType\": \"%s\",\n"
+ + " \"dsseEnvelope\": {\n"
+ + " \"payload\": \"%s\",\n"
+ + " \"payloadType\": \"%s\",\n"
+ + " \"signatures\": [{\"sig\": \"%s\"}]\n"
+ + " }\n"
+ + "}",
+ Const.SIGSTORE_BUNDLE_MEDIA_TYPE,
+ b64.encodeToString(payload),
+ Const.IN_TOTO_PAYLOAD_TYPE,
+ b64.encodeToString(signature));
}
private static String toPem(PublicKey key) {
String b64 =
Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)).encodeToString(key.getEncoded());
- return """
- -----BEGIN PUBLIC KEY-----
- %s
- -----END PUBLIC KEY-----
- """
- .formatted(b64);
+ return String.format("-----BEGIN PUBLIC KEY-----\n" + "%s\n" + "-----END PUBLIC KEY-----\n", b64);
}
}
diff --git a/src/test/java/land/oras/utils/TomlUtilsTest.java b/src/test/java/land/oras/utils/TomlUtilsTest.java
index 7f13eff6..eb52102e 100644
--- a/src/test/java/land/oras/utils/TomlUtilsTest.java
+++ b/src/test/java/land/oras/utils/TomlUtilsTest.java
@@ -53,10 +53,7 @@ void failToParseTomlString() {
@Test
@SuppressWarnings("unchecked")
void shouldParseToml() {
- String tomlMap = """
- key1 = "value1"
- key2 = "value2"
- """;
+ String tomlMap = "key1 = \"value1\"\n" + "key2 = \"value2\"\n";
Map map = TomlUtils.fromToml(tomlMap, Map.class);
assertNotNull(map);
assertEquals(2, map.size());
@@ -67,10 +64,7 @@ void shouldParseToml() {
@Test
@SuppressWarnings("unchecked")
void shouldParseTomlFile() throws IOException {
- String tomlMap = """
- key1 = "value1"
- key2 = "value2"
- """;
+ String tomlMap = "key1 = \"value1\"\n" + "key2 = \"value2\"\n";
Path tomlFile = dir.resolve("test.toml");
Files.writeString(tomlFile, tomlMap);
Map map = TomlUtils.fromToml(tomlFile, Map.class);
@@ -87,10 +81,7 @@ void shouldConvertToToml() {
map.put("key2", "value2");
String toml = TomlUtils.toToml(map);
assertNotNull(toml);
- String expected = """
- key1 = 'value1'
- key2 = 'value2'
- """;
+ String expected = "key1 = 'value1'\n" + "key2 = 'value2'\n";
assertEquals(expected, toml);
}
}
diff --git a/src/test/java/land/oras/utils/YamlUtilsTest.java b/src/test/java/land/oras/utils/YamlUtilsTest.java
index 571eb6bd..5eb663b2 100644
--- a/src/test/java/land/oras/utils/YamlUtilsTest.java
+++ b/src/test/java/land/oras/utils/YamlUtilsTest.java
@@ -53,12 +53,7 @@ void failToParseYamlString() {
@Test
@SuppressWarnings("unchecked")
void shouldParseYaml() {
- String yamlMap =
- """
- ---
- key1: "value1"
- key2: "value2"
- """;
+ String yamlMap = "---\n" + "key1: \"value1\"\n" + "key2: \"value2\"\n";
Map map = YamlUtils.fromYaml(yamlMap, Map.class);
assertNotNull(map);
assertEquals(2, map.size());
@@ -69,12 +64,7 @@ void shouldParseYaml() {
@Test
@SuppressWarnings("unchecked")
void shouldParseYamlFile() throws IOException {
- String yamlMap =
- """
- ---
- key1: "value1"
- key2: "value2"
- """;
+ String yamlMap = "---\n" + "key1: \"value1\"\n" + "key2: \"value2\"\n";
Path yamlFile = dir.resolve("test.yaml");
Files.writeString(yamlFile, yamlMap);
Map map = YamlUtils.fromYaml(yamlFile, Map.class);
@@ -97,11 +87,7 @@ void shouldConvertToYaml() {
map.put("key2", "value2");
String yaml = YamlUtils.toYaml(map);
assertNotNull(yaml);
- String expected = """
- ---
- key1: "value1"
- key2: "value2"
- """;
+ String expected = "---\n" + "key1: \"value1\"\n" + "key2: \"value2\"\n";
assertEquals(expected, yaml);
}
}
diff --git a/src/test/java/land/oras/utils/ZotContainer.java b/src/test/java/land/oras/utils/ZotContainer.java
index a63d1c1a..862e062a 100644
--- a/src/test/java/land/oras/utils/ZotContainer.java
+++ b/src/test/java/land/oras/utils/ZotContainer.java
@@ -44,23 +44,21 @@ public ZotContainer() {
copyFileToContainer(authFile, "/etc/zot/auth.htpasswd");
// language=JSON
- String configJson =
- """
- {
- "storage": { "rootDirectory": "/var/lib/registry" },
- "http": {
- "address": "0.0.0.0",
- "port": %s,
- "auth": {
- "htpasswd": { "path": "/etc/zot/auth.htpasswd" }
- }
- },
- "extensions": {
- "search": { "enable": true }
- }
- }
- """
- .formatted(ZOT_PORT);
+ String configJson = String.format(
+ "{\n"
+ + " \"storage\": { \"rootDirectory\": \"/var/lib/registry\" },\n"
+ + " \"http\": {\n"
+ + " \"address\": \"0.0.0.0\",\n"
+ + " \"port\": %s,\n"
+ + " \"auth\": {\n"
+ + " \"htpasswd\": { \"path\": \"/etc/zot/auth.htpasswd\" }\n"
+ + " }\n"
+ + " },\n"
+ + " \"extensions\": {\n"
+ + " \"search\": { \"enable\": true }\n"
+ + " }\n"
+ + "}\n",
+ ZOT_PORT);
writeConfig(configJson);
} catch (Exception e) {
diff --git a/src/test/java/land/oras/utils/ZotTlsContainer.java b/src/test/java/land/oras/utils/ZotTlsContainer.java
index 2c3328ae..9bd12193 100644
--- a/src/test/java/land/oras/utils/ZotTlsContainer.java
+++ b/src/test/java/land/oras/utils/ZotTlsContainer.java
@@ -66,24 +66,22 @@ public ZotTlsContainer() {
copyFileToContainer(serverKeyPath, "/etc/zot/server-key.pem");
// language=JSON
- String configJson =
- """
- {
- "storage": { "rootDirectory": "/var/lib/registry" },
- "http": {
- "address": "0.0.0.0",
- "port": %s,
- "tls": {
- "cert": "/etc/zot/server.pem",
- "key": "/etc/zot/server-key.pem"
- }
- },
- "extensions": {
- "search": { "enable": true }
- }
- }
- """
- .formatted(ZOT_PORT);
+ String configJson = String.format(
+ "{\n"
+ + " \"storage\": { \"rootDirectory\": \"/var/lib/registry\" },\n"
+ + " \"http\": {\n"
+ + " \"address\": \"0.0.0.0\",\n"
+ + " \"port\": %s,\n"
+ + " \"tls\": {\n"
+ + " \"cert\": \"/etc/zot/server.pem\",\n"
+ + " \"key\": \"/etc/zot/server-key.pem\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"extensions\": {\n"
+ + " \"search\": { \"enable\": true }\n"
+ + " }\n"
+ + "}\n",
+ ZOT_PORT);
writeConfig(configJson);
} catch (Exception e) {
diff --git a/src/test/java/land/oras/utils/ZotUnsecureContainer.java b/src/test/java/land/oras/utils/ZotUnsecureContainer.java
index ea7a136a..f5d98d60 100644
--- a/src/test/java/land/oras/utils/ZotUnsecureContainer.java
+++ b/src/test/java/land/oras/utils/ZotUnsecureContainer.java
@@ -33,20 +33,18 @@ public ZotUnsecureContainer() {
setWaitStrategy(Wait.forHttp("/v2/_catalog").forPort(ZOT_PORT).forStatusCode(200));
// language=JSON
- String configJson =
- """
- {
- "storage": { "rootDirectory": "/var/lib/registry" },
- "http": {
- "address": "0.0.0.0",
- "port": %s
- },
- "extensions": {
- "search": { "enable": true }
- }
- }
- """
- .formatted(ZOT_PORT);
+ String configJson = String.format(
+ "{\n"
+ + " \"storage\": { \"rootDirectory\": \"/var/lib/registry\" },\n"
+ + " \"http\": {\n"
+ + " \"address\": \"0.0.0.0\",\n"
+ + " \"port\": %s\n"
+ + " },\n"
+ + " \"extensions\": {\n"
+ + " \"search\": { \"enable\": true }\n"
+ + " }\n"
+ + "}\n",
+ ZOT_PORT);
writeConfig(configJson);
}
}
diff --git a/updatecli/updatecli.d/maven.yaml b/updatecli/updatecli.d/maven.yaml
deleted file mode 100644
index 86cd11bd..00000000
--- a/updatecli/updatecli.d/maven.yaml
+++ /dev/null
@@ -1,66 +0,0 @@
----
-name: Bump the Maven
-
-scms:
- default:
- kind: github
- spec:
- user: "{{ .github.user }}"
- email: "{{ .github.email }}"
- owner: "{{ .github.owner }}"
- repository: "{{ .github.repository }}"
- token: "{{ requiredEnv .github.token }}"
- username: "{{ .github.username }}"
- branch: "{{ .github.branch }}"
-
-sources:
- mavenLatestVersion:
- kind: githubrelease
- spec:
- owner: "apache"
- repository: "maven"
- token: "{{ requiredEnv .github.token }}"
- username: "{{ .github.username }}"
- versionFilter:
- kind: regex
- pattern: "maven-3.(\\d{1,2}).(\\d{1,2})$"
- transformers:
- - trimprefix: "maven-"
-
-targets:
- updatePom:
- name: "Update the value in the pom.xml file"
- kind: file
- sourceid: mavenLatestVersion
- spec:
- file: pom.xml
- matchPattern: "\\[(\\d.\\d.\\d{1,2}),\\)"
- replacePattern: '[{{ source "mavenLatestVersion" }},)'
- scmid: default
- updateSdkmanRC:
- name: "Update the value in the .sdkmanrc file"
- kind: file
- sourceid: mavenLatestVersion
- spec:
- file: .sdkmanrc
- matchPattern: "maven=(\\d.\\d.\\d{1,2})"
- replacePattern: 'maven={{ source "mavenLatestVersion" }}'
- scmid: default
- updateWorkflowFiles:
- name: "Update the value in the .sdkmanrc file"
- kind: file
- sourceid: mavenLatestVersion
- spec:
- file: .github/vars/maven-version.txt
- matchPattern: "(\\d.\\d.\\d{1,2})"
- replacePattern: '{{ source "mavenLatestVersion" }}'
- scmid: default
-
-actions:
- default:
- kind: github/pullrequest
- scmid: default
- title: 'Bump Maven {{ source "mavenLatestVersion" }}'
- spec:
- labels:
- - dependencies
diff --git a/updatecli/updatecli.d/zot.yaml b/updatecli/updatecli.d/zot.yaml
deleted file mode 100644
index f659e55a..00000000
--- a/updatecli/updatecli.d/zot.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
----
-name: Bump zot test container
-
-scms:
- default:
- kind: github
- spec:
- user: "{{ .github.user }}"
- email: "{{ .github.email }}"
- owner: "{{ .github.owner }}"
- repository: "{{ .github.repository }}"
- token: "{{ requiredEnv .github.token }}"
- username: "{{ .github.username }}"
- branch: "{{ .github.branch }}"
-
-sources:
- zotLatestVersion:
- kind: githubrelease
- spec:
- owner: project-zot
- repository: zot
- token: "{{ requiredEnv .github.token }}"
- username: "{{ .github.username }}"
- versioning:
- kind: semver
-
-conditions:
- zotImageExists:
- name: zot image must exists
- kind: dockerimage
- spec:
- image: ghcr.io/project-zot/zot-minimal
-
-targets:
- updateZotImages:
- name: "Update the value in the TestImages.java file"
- kind: file
- sourceid: zotLatestVersion
- spec:
- file: src/test/java/land/oras/utils/TestImages.java
- matchPattern: "ghcr.io/project-zot/zot-minimal:v(\\d.\\d.\\d{1,2})"
- replacePattern: 'ghcr.io/project-zot/zot-minimal:{{ source "zotLatestVersion" }}'
- scmid: default
-
-actions:
- default:
- kind: github/pullrequest
- scmid: default
- title: 'Bump zot {{ source "zotLatestVersion" }}'
- spec:
- labels:
- - tests
- - dependencies
diff --git a/updatecli/values.github-action.yaml b/updatecli/values.github-action.yaml
deleted file mode 100644
index 89dd0020..00000000
--- a/updatecli/values.github-action.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-github:
- user: "github-actions[bot]"
- email: "41898282+github-actions[bot]@users.noreply.github.com"
- username: "github-actions"
- token: "UPDATECLI_GITHUB_TOKEN"
- branch: "main"
- owner: "oras-project"
- repository: "oras-java"