Skip to content

Commit

Permalink
feat: adds publish action (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgilman authored Sep 5, 2024
1 parent 5e6d7ea commit af1ae6e
Show file tree
Hide file tree
Showing 21 changed files with 13,758 additions and 22 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
^build.*
^check.*
^test.*
publish
check:
uses: ./.github/workflows/run.yml
Expand All @@ -46,4 +47,11 @@ jobs:
needs: [discover, check, build]
with:
earthfiles: ${{ toJson(fromJson(needs.discover.outputs.result)['^test.*']) }}
forge_version: ${{ inputs.forge_version }}

publish:
uses: ./.github/workflows/publish.yml
needs: [discover, check, build, test]
with:
earthfiles: ${{ toJson(fromJson(needs.discover.outputs.result)['publish']) }}
forge_version: ${{ inputs.forge_version }}
62 changes: 62 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
on:
workflow_call:
inputs:
earthfiles:
description: |
A JSON list of Earthfile paths+targets to use for publishing
required: true
type: string
forge_version:
description: |
The version of the forge CLI to install (use 'local' for testing)
required: true
type: string

jobs:
run:
name: ${{ matrix.earthfile }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
earthfile: ${{ fromJson(inputs.earthfiles) }}
steps:
- uses: actions/checkout@v4
- name: Setup CI
uses: ./forge/actions/setup
with:
forge_version: ${{ inputs.forge_version }}
- name: Run
id: run
uses: ./forge/actions/run
with:
path: ${{ matrix.earthfile }}
- name: Get image and project
id: image
if: github.ref_name == github.event.repository.default_branch
run: |
EARTHFILE='${{ matrix.earthfile }}'
PROJECT="${EARTHFILE%+*}"
TARGET="${EARTHFILE#*+}"
RESULT='${{ steps.run.outputs.result }}'
# If the path to the project is just ".", then we omit it
if [[ "$PROJECT" == "." ]]; then
EARTHFILE="+$TARGET"
fi
IMAGE="$(echo "$RESULT" | jq -r ".images[\"$EARTHFILE\"]")"
if [[ "$IMAGE" == "null" ]]; then
echo "::error file=${PROJECT}/Earthfile::No images produced. Cannot publish."
exit 1
fi
echo "image=$IMAGE" >> $GITHUB_OUTPUT
echo "project=$PROJECT" >> $GITHUB_OUTPUT
- name: Publish
uses: ./forge/actions/publish
if: github.ref_name == github.event.repository.default_branch
with:
image: ${{ steps.image.outputs.image }}
project: ${{ steps.image.outputs.project }}
6 changes: 6 additions & 0 deletions blueprint.cue
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,11 @@ global: {

github: registry: "ghcr.io"
}
tagging: {
aliases: {
forge: "forge/cli"
}
strategy: "commit"
}
}
}
35 changes: 27 additions & 8 deletions blueprint/schema/_embed/schema.cue
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@ package schema
// Project contains the configuration for the project.
#Project: {
// Name contains the name of the project.
name: string @go(Name)
name: =~"^[a-z][a-z0-9_-]*$" @go(Name)

// Container is the name that the container will be built as.
container: (_ | *name) & {
string
} @go(Container)

// CI contains the configuration for the CI system.
// +optional
ci?: #ProjectCI @go(CI)
}
#ProjectCI: {
// Targets configures the individual targets that are run by the CI system.
// +optional
targets?: {
[string]: #Target
} @go(Targets,map[string]Target)
}

// CI contains the configuration for the CI system.
#GlobalCI: {
Expand All @@ -47,6 +45,17 @@ package schema
// Registries contains the container registries to push images to.
// +optional
registries?: [...string] @go(Registries,[]string)

// Tagging contains the tagging configuration for the CI system.
// +optional
tagging?: #Tagging @go(Tagging)
}
#ProjectCI: {
// Targets configures the individual targets that are run by the CI system.
// +optional
targets?: {
[string]: #Target
} @go(Targets,map[string]Target)
}

// Providers contains the configuration for the providers being used by the CI system.
Expand Down Expand Up @@ -132,6 +141,16 @@ package schema
} @go(Maps,map[string]string)
}
version: "1.0"
#Tagging: {
// Aliases contains the aliases to use for git tags.
// +optional
aliases?: {
[string]: string
} @go(Aliases,map[string]string)

// Strategy contains the tagging strategy to use for containers.
strategy: "commit" @go(Strategy)
}

// Target contains the configuration for a single target.
#Target: {
Expand Down
28 changes: 22 additions & 6 deletions blueprint/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,14 @@ type Project struct {
// Name contains the name of the project.
Name string `json:"name"`

// Container is the name that the container will be built as.
Container string `json:"container"`

// CI contains the configuration for the CI system.
// +optional
CI ProjectCI `json:"ci"`
}

type ProjectCI struct {
// Targets configures the individual targets that are run by the CI system.
// +optional
Targets map[string]Target `json:"targets"`
}

// CI contains the configuration for the CI system.
type GlobalCI struct {
// Providers contains the configuration for the providers being used by the CI system.
Expand All @@ -56,6 +53,16 @@ type GlobalCI struct {
// Registries contains the container registries to push images to.
// +optional
Registries []string `json:"registries"`

// Tagging contains the tagging configuration for the CI system.
// +optional
Tagging Tagging `json:"tagging"`
}

type ProjectCI struct {
// Targets configures the individual targets that are run by the CI system.
// +optional
Targets map[string]Target `json:"targets"`
}

// Providers contains the configuration for the providers being used by the CI system.
Expand Down Expand Up @@ -139,6 +146,15 @@ type Secret struct {
Maps map[string]string `json:"maps"`
}

type Tagging struct {
// Aliases contains the aliases to use for git tags.
// +optional
Aliases map[string]string `json:"aliases"`

// Strategy contains the tagging strategy to use for containers.
Strategy string `json:"strategy"`
}

// Target contains the configuration for a single target.
type Target struct {
// Args contains the arguments to pass to the target.
Expand Down
28 changes: 22 additions & 6 deletions blueprint/schema/schema_go_gen.cue
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,14 @@ package schema
// Name contains the name of the project.
name: string @go(Name)

// Container is the name that the container will be built as.
container: string @go(Container)

// CI contains the configuration for the CI system.
// +optional
ci?: #ProjectCI @go(CI)
}

#ProjectCI: {
// Targets configures the individual targets that are run by the CI system.
// +optional
targets?: {[string]: #Target} @go(Targets,map[string]Target)
}

// CI contains the configuration for the CI system.
#GlobalCI: {
// Providers contains the configuration for the providers being used by the CI system.
Expand All @@ -50,6 +47,16 @@ package schema
// Registries contains the container registries to push images to.
// +optional
registries?: [...string] @go(Registries,[]string)

// Tagging contains the tagging configuration for the CI system.
// +optional
tagging?: #Tagging @go(Tagging)
}

#ProjectCI: {
// Targets configures the individual targets that are run by the CI system.
// +optional
targets?: {[string]: #Target} @go(Targets,map[string]Target)
}

// Providers contains the configuration for the providers being used by the CI system.
Expand Down Expand Up @@ -133,6 +140,15 @@ package schema
maps?: {[string]: string} @go(Maps,map[string]string)
}

#Tagging: {
// Aliases contains the aliases to use for git tags.
// +optional
aliases?: {[string]: string} @go(Aliases,map[string]string)

// Strategy contains the tagging strategy to use for containers.
strategy: string @go(Strategy)
}

// Target contains the configuration for a single target.
#Target: {
// Args contains the arguments to pass to the target.
Expand Down
9 changes: 9 additions & 0 deletions blueprint/schema/schema_overrides.cue
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ package schema
#Blueprint: {
version: string & =~"^\\d+\\.\\d+"
}

#Project: {
name: _ & =~"^[a-z][a-z0-9_-]*$"
container: _ | *name
}

#Tagging: {
strategy: _ & "commit"
}
File renamed without changes.
66 changes: 66 additions & 0 deletions forge/actions/publish/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Publish Action

The publish action pushes Docker images to remote registries using settings from a project's blueprint file.
It automatically handles the configured tagging strategy as well as properly handling git tags.

## Usage

```yaml
name: Run Publish
on:
push:

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Setup
uses: input-output-hk/catalyst-forge/forge/actions/setup@master
- name: Publish
uses: input-output-hk/catalyst-forge/forge/actions/discover@master
with:
project: ./my/project/path
container: container:tag
```
The given project _must_ have a blueprint at the root of the path which, at the very least, declares a project name.
By default, the `container` property of a project uses the project name if not specified.
Alternatively, the `container` field can be set explicitly.
The `container` input to the publish action _must_ match an existing container image in the local Docker daemon.
The given container name is discarded and the value of the `container` field is used for naming the container.

The final tags the container is published with are determined by the blueprint configuration and the git context:

- The `global.ci.tagging.strategy` configuration property determines the default tag given to all images
- If the git context contains a git tag, then the publish action may or may not publish an image with the tag:
- If the tag is in the "mono-repo" style (`some/path/v1.0.0`)
- If the path (`some/path`) matches an alias in `global.ci.tagging.strategy.aliases`, and the value of the alias matches the
given project, then the tag is used
- If the path does not match an alias, but the path itself matches the given project, then the tag is used
- If none of the above are true, the tag is assumed to be for a different project and is skipped
- If the tag is any other style, it's used as-is (no modifications)

The following table provides an example of how the git tag is used in various contexts:

| Project | Git tag | Aliases | Image tag |
| ------------- | -------------------- | ------------------------ | --------- |
| `my/cool/cli` | None | None | Not used |
| `my/cool/cli` | `v1.0.0` | None | `v1.0.0` |
| `my/cool/cli` | `my/v1.0.0` | None | Not used |
| `my/cool/cli` | `my/cool/cli/v1.0.0` | None | `v1.0.0` |
| `my/cool/cli` | `cli/v1.0.0` | `{"cli": "my/cool/cli"}` | `v1.0.0` |

After processing any additional tags, the container is retagged with each generated tag and pushed to all registries configured in
`global.ci.registries`.
The publish action assumes the local Docker daemon is already authenticated to any configured registries.

## Inputs

| Name | Description | Required | Default |
| ------- | ------------------------------------------------ | -------- | ------- |
| project | The relative path to the project (from git root) | Yes | N/A |
| image | The existing container image to publish | Yes | N/A |
13 changes: 13 additions & 0 deletions forge/actions/publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Publish
description: Publish container images
inputs:
project:
description: The path to the project
required: true
image:
description: The full image name (name:tag) to publish
required: true

runs:
using: node20
main: dist/index.js
Loading

0 comments on commit af1ae6e

Please sign in to comment.