Skip to content
124 changes: 124 additions & 0 deletions .github/actions/mirror-maven-jar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Mirror Public JAR to CodeArtifact <!-- omit in toc -->

A composite GitHub Action that mirrors a single pre-built public JAR into AWS CodeArtifact, skipping the upload if that version already exists. The JAR can be downloaded from a remote URL or supplied as a local file.

- [How-to guides](#how-to-guides)
- [Reference](#reference)
- [Explanation](#explanation)

## How-to guides

### Mirror a public JAR from a URL

`setup-codeartifact` must run earlier in the **same job** to configure AWS
credentials and Maven `settings.xml`.

```yaml
jobs:
mirror:
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC role assumption
contents: read
steps:
- uses: actions/checkout@v4

- name: Authenticate with CodeArtifact
uses: OvertureMaps/workflows/.github/actions/setup-codeartifact@main
with:
aws-role-arn: arn:aws:iam::123456789012:role/codeartifact-publisher
codeartifact-domain: overture
codeartifact-domain-owner: "123456789012"
codeartifact-repository: maven-third-party

- name: Mirror libphonenumber
uses: OvertureMaps/workflows/.github/actions/mirror-maven-jar@main
with:
group-id: com.googlecode.libphonenumber
artifact-id: libphonenumber
version: "8.13.48"
jar-url: https://repo1.maven.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.48/libphonenumber-8.13.48.jar
codeartifact-domain: overture
codeartifact-domain-owner: "123456789012"
codeartifact-repository: maven-third-party
aws-region: us-west-2
```

<details>
<summary>Mirror a local JAR file</summary>

Use `jar-path` instead of `jar-url` for a JAR already on disk:

```yaml
- name: Mirror local JAR
uses: OvertureMaps/workflows/.github/actions/mirror-maven-jar@main
with:
group-id: com.example
artifact-id: my-lib
version: "1.0.0"
jar-path: ./vendor/my-lib-1.0.0.jar
codeartifact-domain: overture
codeartifact-domain-owner: "123456789012"
codeartifact-repository: maven-third-party
aws-region: us-west-2
```

</details>

> Pin to a commit SHA rather than `@main` for reproducible builds, e.g.
> `uses: OvertureMaps/workflows/.github/actions/mirror-maven-jar@<sha>`.

## Reference

### Inputs

- `group-id` (**required**): Maven groupId (e.g. `com.googlecode.libphonenumber`).
- `artifact-id` (**required**): Maven artifactId (e.g. `libphonenumber`).
- `version` (**required**): Maven version to mirror (e.g. `8.13.48`).
- `jar-url` (optional): Remote URL to download the JAR from. Mutually exclusive with `jar-path`.
- `jar-path` (optional): Local path to an existing JAR file. Mutually exclusive with `jar-url`.
- `codeartifact-domain` (**required**): CodeArtifact domain name.
- `codeartifact-domain-owner` (**required**): AWS account ID that owns the CodeArtifact domain.
- `codeartifact-repository` (**required**): CodeArtifact repository name.
- `aws-region` (**required**): AWS region where CodeArtifact is hosted.

### Outputs

This action has no outputs. It either uploads the JAR or skips when the version
already exists.

### Prerequisites

This action neither authenticates nor installs a toolchain, so the job must
provide both before calling it:

- **A JDK + Maven on the runner.** The action invokes `mvn deploy:deploy-file`,
so `mvn` must be on `PATH` — typically via `actions/setup-java` (with
`distribution`/`java-version`), which also provisions Maven. GitHub-hosted
runners include Maven by default; minimal or self-hosted runners may not.
- **`setup-codeartifact` called earlier in the same job.** This action has no
internal authentication step — it relies on the AWS credentials and
`~/.m2/settings.xml` that `setup-codeartifact` configures.

## Explanation

### Idempotent mirroring

The action first calls `aws codeartifact describe-package-version`. If the
version already exists, the download and publish steps are skipped, so reruns
are safe and cheap. Only missing versions are fetched and deployed via
`mvn deploy:deploy-file` with a generated pom.

### URL vs local file

`jar-url` and `jar-path` are mutually exclusive. With `jar-url`, the JAR is
downloaded to `_downloaded.jar` and published; with `jar-path`, the existing
file is published in place. When the version is not yet mirrored, a validation
step enforces that exactly one of the two is provided — supplying neither or
both fails fast with a clear error rather than a confusing Maven failure.

### Why it has no auth step

Unlike `publish-maven-to-codeartifact`, this action assumes authentication already
happened in the same job via `setup-codeartifact`. This lets a single job mirror
many JARs after one authentication step, avoiding repeated OIDC role assumptions.
112 changes: 112 additions & 0 deletions .github/actions/mirror-maven-jar/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Mirror Public JAR to CodeArtifact
description: >
Mirrors a single pre-built public JAR into AWS CodeArtifact, skipping if the
version already exists. Supports downloading from a remote URL or using a
local file.
Prerequisite: setup-codeartifact must have been called in the same job to
configure AWS credentials and Maven settings.

inputs:
group-id:
description: Maven groupId (e.g. com.googlecode.libphonenumber)
required: true
artifact-id:
description: Maven artifactId (e.g. libphonenumber)
required: true
version:
description: Maven version to mirror (e.g. 8.13.48)
required: true
jar-url:
description: Remote URL to download the JAR from (mutually exclusive with jar-path)
required: false
default: ""
jar-path:
description: Local path to an existing JAR file (mutually exclusive with jar-url)
required: false
default: ""
codeartifact-domain:
description: CodeArtifact domain name
required: true
codeartifact-domain-owner:
description: AWS account ID that owns the CodeArtifact domain
required: true
codeartifact-repository:
description: CodeArtifact repository name
required: true
aws-region:
description: AWS region where CodeArtifact is hosted
required: true

runs:
using: "composite"
steps:
- name: Check if package already exists in CodeArtifact
id: check
shell: bash
env:
CODEARTIFACT_DOMAIN: ${{ inputs.codeartifact-domain }}
CODEARTIFACT_DOMAIN_OWNER: ${{ inputs.codeartifact-domain-owner }}
CODEARTIFACT_REPOSITORY: ${{ inputs.codeartifact-repository }}
GROUP_ID: ${{ inputs.group-id }}
ARTIFACT_ID: ${{ inputs.artifact-id }}
VERSION: ${{ inputs.version }}
AWS_REGION: ${{ inputs.aws-region }}
run: |
if aws codeartifact describe-package-version \
--domain "$CODEARTIFACT_DOMAIN" \
--domain-owner "$CODEARTIFACT_DOMAIN_OWNER" \
--repository "$CODEARTIFACT_REPOSITORY" \
--format maven \
--namespace "$GROUP_ID" \
--package "$ARTIFACT_ID" \
--package-version "$VERSION" \
--region "$AWS_REGION" > /dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Validate JAR source inputs
if: steps.check.outputs.exists == 'false'
shell: bash
env:
JAR_URL: ${{ inputs.jar-url }}
JAR_PATH: ${{ inputs.jar-path }}
run: |
if [ -n "$JAR_URL" ] && [ -n "$JAR_PATH" ]; then
echo "Error: provide only one of jar-url or jar-path, not both." >&2
exit 1
fi
if [ -z "$JAR_URL" ] && [ -z "$JAR_PATH" ]; then
echo "Error: version is not yet mirrored; provide exactly one of jar-url or jar-path." >&2
exit 1
fi

- name: Download JAR from remote URL
if: steps.check.outputs.exists == 'false' && inputs.jar-url != ''
shell: bash
env:
JAR_URL: ${{ inputs.jar-url }}
run: curl -fsSL -o _downloaded.jar "$JAR_URL"

- name: Publish JAR to CodeArtifact
if: steps.check.outputs.exists == 'false'
shell: bash
env:
CODEARTIFACT_DOMAIN: ${{ inputs.codeartifact-domain }}
GROUP_ID: ${{ inputs.group-id }}
ARTIFACT_ID: ${{ inputs.artifact-id }}
VERSION: ${{ inputs.version }}
JAR_FILE: ${{ inputs.jar-path != '' && inputs.jar-path || '_downloaded.jar' }}
REPO_URL: "https://${{ inputs.codeartifact-domain }}-${{ inputs.codeartifact-domain-owner }}.d.codeartifact.${{ inputs.aws-region }}.amazonaws.com/maven/${{ inputs.codeartifact-repository }}/"
run: |
mvn deploy:deploy-file -ntp \
-Dfile="$JAR_FILE" \
-DgroupId="$GROUP_ID" \
-DartifactId="$ARTIFACT_ID" \
-Dversion="$VERSION" \
-Dpackaging=jar \
-DgeneratePom=true \
-DrepositoryId="$CODEARTIFACT_DOMAIN" \
-Durl="$REPO_URL" \
--settings ~/.m2/settings.xml
124 changes: 124 additions & 0 deletions .github/actions/publish-maven-to-codeartifact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Build and Publish Maven Project to CodeArtifact <!-- omit in toc -->

A composite GitHub Action that builds a Maven project from source and publishes its artifacts to AWS CodeArtifact. It sets up the JDK, authenticates with CodeArtifact, optionally overrides the version for dev builds, deploys, and prints the shaded JAR manifest.

- [How-to guides](#how-to-guides)
- [Reference](#reference)
- [Explanation](#explanation)

## How-to guides

### Publish a release build

```yaml
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC role assumption
contents: read
steps:
- uses: actions/checkout@v4

- name: Build and publish to CodeArtifact
uses: OvertureMaps/workflows/.github/actions/publish-maven-to-codeartifact@main
with:
aws-role-arn: arn:aws:iam::123456789012:role/codeartifact-publisher
codeartifact-domain: overture
codeartifact-domain-owner: "123456789012"
codeartifact-repository: maven-releases
```

<details>
<summary>Publish a dev build with an overridden version</summary>

Set `version` to publish a dev-named artifact. The pom version is overridden via
`mvn versions:set` and the build runs with `-Denv=dev`.

```yaml
- name: Publish dev build
uses: OvertureMaps/workflows/.github/actions/publish-maven-to-codeartifact@main
with:
aws-role-arn: arn:aws:iam::123456789012:role/codeartifact-publisher
codeartifact-domain: overture
codeartifact-domain-owner: "123456789012"
codeartifact-repository: maven-snapshots
version: 1.4.0-dev-${{ github.run_number }}
```

</details>

> Pin to a commit SHA rather than `@main` for reproducible builds, e.g.
> `uses: OvertureMaps/workflows/.github/actions/publish-maven-to-codeartifact@<sha>`.

## Reference

### Inputs

- `aws-role-arn` (**required**): IAM role ARN to assume via OIDC for CodeArtifact access.
- `aws-region` (optional): AWS region where CodeArtifact is hosted. Default `us-west-2`.
- `codeartifact-domain` (**required**): CodeArtifact domain name.
- `codeartifact-domain-owner` (**required**): AWS account ID that owns the CodeArtifact domain.
- `codeartifact-repository` (**required**): CodeArtifact repository name.
- `version` (optional): Artifact version override. When set, the pom version is overridden via `mvn versions:set` and the project is built with dev naming (`-Denv=dev`). When empty (default), the project's release version is published unchanged (`-Denv=release`).
- `working-directory` (optional): Directory containing the Maven project (its `pom.xml` and `.java-version`). Defaults to the repository root (`.`). Set this to publish a project that lives in a subdirectory.
- `commit` (optional): Commit SHA recorded as `build.commit` in the JAR manifest. Defaults to the checked-out workspace HEAD (`git rev-parse HEAD`), which matches the tree actually built — unlike `github.sha`, which is the merge-ref SHA on `pull_request` events. Override only if the build tree is not a git checkout.

### Outputs

This action has no outputs. It deploys the project's artifacts to CodeArtifact
and prints the shaded JAR's `MANIFEST.MF` to the step log.

### Permissions

```yaml
permissions:
id-token: write
contents: read
```

The assumed IAM role must allow `codeartifact:GetAuthorizationToken`,
`sts:GetServiceBearerToken`, and the publish actions for the target repository.

### Requirements

- A `.java-version` file in the `working-directory` (consumed by `actions/setup-java`).
- A Maven build that produces a `*-shaded.jar` under `target/`.
- A git checkout (the default `actions/checkout` state) so `build.commit` can
default to the workspace HEAD. Pass the `commit` input if the tree is not a
git checkout.

## Explanation

### What it does

The action runs four phases in one job step: JDK setup (Temurin, version from
`.java-version`, Maven cache), CodeArtifact authentication (delegated to
`setup-codeartifact`), an optional `mvn versions:set` when `version` is
supplied, then `mvn clean deploy` with build provenance properties
(`build.branch`, `build.commit`, `workflow.run_id`, `workflow.run_number`). Tests
are skipped during deploy; run them in a separate CI step.

### Commit provenance

`build.commit` defaults to the checked-out workspace HEAD (`git rev-parse HEAD`),
not `github.sha`. On `pull_request` events `github.sha` is the ephemeral
merge-ref SHA, which diverges from the commit a caller actually checked out
(e.g. `pull_request.head.sha`). Deriving it from HEAD keeps the manifest's commit
in lock-step with the built tree regardless of the caller's checkout strategy;
pass the `commit` input to override.

### Release vs dev publishing

The `version` input switches between two modes. Empty publishes the pom's
release version unchanged with `-Denv=release`. A non-empty value overrides the
pom version and builds with `-Denv=dev`, enabling dev-named snapshot publishing
without editing the pom in source control.

### Self-referential authentication

Inside a composite action, `uses: ./...` resolves against the **caller's**
checkout, not this repo. So the internal authentication step references
`OvertureMaps/workflows/.github/actions/setup-codeartifact@main` by full path
(with a `zizmor: ignore[unpinned-uses]` comment) rather than a `./` relative
path. The `@main` ref is tightened to a commit SHA in a follow-up once merged.
Loading
Loading