Skip to content

Commit 824a3f4

Browse files
authored
feat(cli): allows filtering by Earthfile content (#185)
1 parent 2f8bb64 commit 824a3f4

File tree

33 files changed

+36950
-192
lines changed

33 files changed

+36950
-192
lines changed

.github/workflows/ci.yml

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ on:
1717
required: false
1818
type: boolean
1919
default: false
20+
tags:
21+
description: A newline separated list of tags to filter Earthly targets by
22+
required: false
23+
type: string
24+
default: ""
25+
skip_release:
26+
description: Skip the release job
27+
required: false
28+
type: boolean
29+
default: false
30+
skip_deploy:
31+
description: Skip the deploy job
32+
required: false
33+
type: boolean
34+
default: false
2035
verbosity:
2136
description: The verbosity level to use
2237
required: false
@@ -36,7 +51,6 @@ env:
3651
FORGE_REGEX_DOCS: ^docs(-.*)?$
3752
FORGE_REGEX_RELEASE: ^release(-.*)?$
3853
FORGE_REGEX_PUBLISH: ^publish(-.*)?$
39-
FORGE_REGEX_NIGHTLY: ^nightly(-.*)?$
4054

4155
jobs:
4256
discover:
@@ -81,6 +95,7 @@ jobs:
8195
${{ env.FORGE_REGEX_RELEASE }}
8296
${{ env.FORGE_REGEX_PUBLISH }}
8397
${{ env.FORGE_REGEX_NIGHTLY }}
98+
tags: ${{ inputs.tags }}
8499
check:
85100
uses: input-output-hk/catalyst-forge/.github/workflows/run.yml@master
86101
needs: [discover]
@@ -125,21 +140,10 @@ jobs:
125140
skip_output: true
126141
verbosity: ${{ inputs.verbosity }}
127142

128-
nightly:
129-
uses: input-output-hk/catalyst-forge/.github/workflows/run.yml@master
130-
needs: [discover, check, build, package]
131-
if: (fromJson(needs.discover.outputs.earthfiles)['^nightly(-.*)?$'] != null) && (inputs.release_only == false) && !failure() && !cancelled() && inputs.nightly == true
132-
with:
133-
earthfiles: ${{ toJson(fromJson(needs.discover.outputs.earthfiles)['^nightly(-.*)?$']) }}
134-
forge_version: ${{ inputs.forge_version }}
135-
local: ${{ inputs.local }}
136-
skip_output: true
137-
verbosity: ${{ inputs.verbosity }}
138-
139143
release:
140144
uses: input-output-hk/catalyst-forge/.github/workflows/release.yml@master
141145
needs: [discover, check, build, test]
142-
if: (fromJson(needs.discover.outputs.releases)[0] != null) && !failure() && !cancelled()
146+
if: (fromJson(needs.discover.outputs.releases)[0] != null) && !inputs.skip_release && !failure() && !cancelled()
143147
with:
144148
releases: ${{ needs.discover.outputs.releases }}
145149
forge_version: ${{ inputs.forge_version }}
@@ -149,15 +153,15 @@ jobs:
149153
deploy:
150154
uses: input-output-hk/catalyst-forge/.github/workflows/deploy.yml@master
151155
needs: [discover, check, build, test, release]
152-
if: (fromJson(needs.discover.outputs.deployments)[0] != null) && !failure() && !cancelled()
156+
if: (fromJson(needs.discover.outputs.deployments)[0] != null) && !inputs.skip_deploy && !failure() && !cancelled()
153157
with:
154158
deployments: ${{ needs.discover.outputs.deployments }}
155159
forge_version: ${{ inputs.forge_version }}
156160
local: ${{ inputs.local }}
157161
verbosity: ${{ inputs.verbosity }}
158162

159163
final:
160-
needs: [check, build, package, test, nightly, release, deploy]
164+
needs: [check, build, package, test, release, deploy]
161165
if: ${{ always() && (contains(needs.*.result, 'failure') || !failure() && !cancelled()) }}
162166
runs-on: ubuntu-latest
163167
steps:

.github/workflows/dogfood.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ jobs:
1717
uses: ./.github/workflows/ci.yml
1818
with:
1919
forge_version: local
20-
verbosity: debug
21-
nightly: true
22-
release_only: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }}
20+
release_only: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') }}
21+
verbosity: debug

actions/discovery/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ inputs:
1010
path:
1111
description: The path to search from
1212
default: "."
13+
tags:
14+
description: A newline separated list of tags to filter Earthly targets by
15+
default: ""
1316
outputs:
1417
deployments:
1518
description: The deployments discovered

actions/discovery/dist/index.js

Lines changed: 24 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

actions/discovery/dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

actions/discovery/src/main.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ const exec = require("@actions/exec");
44
async function run() {
55
try {
66
const absolute = core.getBooleanInput("absolute", { required: false });
7-
const path = core.getInput("path", { required: true });
87
const filters = core.getInput("filters", { required: false });
8+
const path = core.getInput("path", { required: true });
9+
const tags = core.getInput("tags", { required: false });
910

1011
await runDeploymentScan(absolute, path);
11-
await runEarthfileScan(filters, absolute, path);
12+
await runEarthfileScan(filters, absolute, path, tags);
1213
await runReleaseScan(absolute, path);
1314
} catch (error) {
1415
core.setFailed(error.message);
@@ -25,7 +26,7 @@ module.exports = {
2526
* @param {string} path The path to scan
2627
*/
2728
async function runDeploymentScan(absolute, path) {
28-
const args = ["-vv", "scan", "--blueprint", "--filter", "project.deployment"];
29+
const args = ["-vv", "scan", "blueprint", "--filter", "project.deployment"];
2930

3031
if (absolute === true) {
3132
args.push("--absolute");
@@ -46,14 +47,15 @@ async function runDeploymentScan(absolute, path) {
4647
* @param {boolean} absolute Whether to use absolute paths or not
4748
* @param {string} path The path to scan
4849
*/
49-
async function runEarthfileScan(filters, absolute, path) {
50-
let args = ["-vv", "scan", "--ci", "--earthfile"];
50+
async function runEarthfileScan(filters, absolute, path, tags) {
51+
let args = ["-vv", "scan", "earthfile", "--enumerate"];
5152

5253
if (absolute === true) {
5354
args.push("--absolute");
5455
}
5556

5657
args = args.concat(filtersToArgs(filters));
58+
args = args.concat(tagsToArgs(tags));
5759
args.push(path);
5860

5961
core.info(`Running forge ${args.join(" ")}`);
@@ -69,7 +71,7 @@ async function runEarthfileScan(filters, absolute, path) {
6971
* @param {string} path The path to scan
7072
*/
7173
async function runReleaseScan(absolute, path) {
72-
const args = ["-vv", "scan", "--blueprint", "--filter", "project.release"];
74+
const args = ["-vv", "scan", "blueprint", "--filter", "project.release"];
7375

7476
if (absolute === true) {
7577
args.push("--absolute");
@@ -103,3 +105,19 @@ function filtersToArgs(input) {
103105

104106
return result;
105107
}
108+
109+
/**
110+
* Converts the tags input string to command line arguments.
111+
* @param {string} input The tags input string
112+
* @returns {string[]} The tags as command line arguments
113+
*/
114+
function tagsToArgs(input) {
115+
const lines = input.trim().split("\n");
116+
117+
const result = [];
118+
for (const line of lines) {
119+
result.push("--tag", line);
120+
}
121+
122+
return result;
123+
}

actions/reject-earthfile/README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Reject Earthfile Action
2+
3+
This GitHub Action scans for Earthfiles that match certain filter expressions and fails the workflow if any matches are found. This is useful for enforcing policies or preventing certain patterns in Earthfiles.
4+
5+
## Inputs
6+
7+
### `filters` (required)
8+
9+
A newline-separated list of filter expressions and error messages. Each expression should be on its own line, followed by its error message on the next line.
10+
11+
**Example:**
12+
13+
```
14+
prod
15+
contain production targets
16+
debug
17+
contain debug targets
18+
```
19+
20+
### `root-path` (optional)
21+
22+
The root path to scan for Earthfiles. Defaults to the current directory (`.`).
23+
24+
### `filter-source` (optional)
25+
26+
The source to filter by. Can be either `"earthfile"` or `"targets"`. Defaults to `"targets"`.
27+
28+
- `"targets"`: Filter by target names in Earthfiles
29+
- `"earthfile"`: Filter by Earthfile contents
30+
31+
### `verbosity` (optional)
32+
33+
The verbosity level for the forge command. Can be `"error"`, `"info"`, or `"debug"`. Defaults to `"info"`.
34+
35+
## Usage
36+
37+
### Basic Example
38+
39+
```yaml
40+
- name: Reject Earthfiles with production targets
41+
uses: ./.github/actions/reject-earthfile
42+
with:
43+
filters: |
44+
prod
45+
contain production targets
46+
```
47+
48+
### Multiple Filters
49+
50+
```yaml
51+
- name: Reject problematic Earthfiles
52+
uses: ./.github/actions/reject-earthfile
53+
with:
54+
filters: |
55+
prod
56+
contain production targets
57+
debug
58+
contain debug targets
59+
test
60+
contain test targets
61+
```
62+
63+
### Filter by Earthfile Contents
64+
65+
```yaml
66+
- name: Reject Earthfiles with certain content
67+
uses: ./.github/actions/reject-earthfile
68+
with:
69+
filters: |
70+
FROM alpine
71+
use Alpine base image
72+
filter-source: "earthfile"
73+
```
74+
75+
### Custom Root Path
76+
77+
```yaml
78+
- name: Reject Earthfiles in specific directory
79+
uses: ./.github/actions/reject-earthfile
80+
with:
81+
filters: |
82+
prod
83+
contain production targets
84+
root-path: "./apps"
85+
```
86+
87+
## Output Format
88+
89+
When rejections are found, the action will output a message in this format:
90+
91+
```
92+
The following Earthfiles contain production targets:
93+
- ./path/to/earthfile1+target1
94+
- ./path/to/earthfile2+target2
95+
96+
The following Earthfiles contain debug targets:
97+
- ./path/to/earthfile3+target3
98+
```
99+
100+
## Filter Expressions
101+
102+
The filter expressions are regular expressions that are matched against either:
103+
104+
- Target names (when `filter-source` is `"targets"`)
105+
- Earthfile contents (when `filter-source` is `"earthfile"`)
106+
107+
### Examples
108+
109+
- `"prod"` - Matches any target or content containing "prod"
110+
- `"^prod"` - Matches targets or content starting with "prod"
111+
- `"prod$"` - Matches targets or content ending with "prod"
112+
- `"prod|staging"` - Matches targets or content containing "prod" or "staging"

actions/reject-earthfile/action.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Reject Earthfile
2+
description: Reject Earthfiles that match certain filter expressions
3+
inputs:
4+
filters:
5+
description: |
6+
Newline-separated list of filter expressions and error messages.
7+
Format: "expr1
8+
error message 1
9+
expr2
10+
error message 2"
11+
Each expression should be on its own line, followed by its error message on the next line.
12+
required: true
13+
root-path:
14+
description: Root path to scan for Earthfiles
15+
required: false
16+
default: "."
17+
filter-source:
18+
description: The source to filter by [earthfile | targets]
19+
required: false
20+
default: "targets"
21+
verbosity:
22+
description: The verbosity level to use
23+
required: false
24+
default: "info"
25+
26+
runs:
27+
using: node20
28+
main: dist/index.js

0 commit comments

Comments
 (0)