|
1 |
| -# Usage |
| 1 | +# Standard Action |
| 2 | + |
| 3 | +_for [Standard] & [Paisano]_. |
| 4 | + |
| 5 | +[Paisano]: https://github.com/paisano-nix |
| 6 | +[Standard]: https://github.com/divnix/std |
| 7 | + |
| 8 | +Don't waste any time on extra work. Use Standard Action to automatically |
| 9 | +detect CI targets that need re-doing; implemented on top of familiar GH Actions. |
| 10 | + |
| 11 | +## Features |
| 12 | + |
| 13 | +- Evaluate once and distribute final build instructions to workers |
| 14 | +- Once configured, `discovery` picks up new targets automatically |
| 15 | +- Optional `proviso` script can detect if work needs to be done |
| 16 | + |
| 17 | +> **Note on `proviso`**: one example is the oci block type which |
| 18 | +> [checks if the image] is already in the registry and only schedules |
| 19 | +> a build if its missing. If `proviso` queries private remote state |
| 20 | +> then the `discovery` environment must provide all authentication |
| 21 | +> prior to running the discovery step. |
| 22 | +
|
| 23 | +[checks if the image]: https://github.com/divnix/std/blob/main/src/std/fwlib/blockTypes/containers-proviso.sh |
| 24 | + |
| 25 | +## Usage |
| 26 | + |
| 27 | +**Minimumn nix version `v2.16.1`** |
2 | 28 |
|
3 | 29 | - Works with https://github.com/divnix/std
|
4 | 30 | - Since GitHub CI doesn't support yaml anchors, explode your file with: `yq '. | explode(.)' ci.raw.yaml > ci.yaml`
|
5 |
| -- To set up AWS Credentials for an S3 Cache, find details [here](https://github.com/aws-actions/configure-aws-credentials) |
6 |
| -- **Warning:** This is still under active development and testing. You're likely better off waiting a little while, still. |
7 |
| - - But it's already being used with success :smile: |
| 31 | + |
| 32 | +### Standalone |
| 33 | + |
| 34 | +```nix |
| 35 | +{ |
| 36 | + /* ... */ |
| 37 | + outputs = {std, ...}@inputs: std.growOn { |
| 38 | + /* ... */ |
| 39 | + cellBlocks = with std.blockTypes; [ |
| 40 | + (installables "packages" {ci.build = true;}) |
| 41 | + (containers "oci-images" {ci.publish = true;}) |
| 42 | + (kubectl "deployments" {ci.apply = true;}) |
| 43 | + ]; |
| 44 | + /* ... */ |
| 45 | + }; |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +<details><summary><h4>GH Action file</h4></summary> |
8 | 50 |
|
9 | 51 | ```yaml
|
10 |
| -# .github/workflows/ci.yml |
11 |
| -name: Standard CI |
| 52 | +# yq '. | explode(.)' this.yml > .github/workflows/std.yml |
| 53 | +name: CI/CD |
12 | 54 |
|
13 | 55 | on:
|
| 56 | + pull_request: |
| 57 | + branches: |
| 58 | + - main |
14 | 59 | push:
|
15 | 60 | branches:
|
16 | 61 | - main
|
17 |
| - workflow_dispatch: |
18 | 62 |
|
19 | 63 | permissions:
|
| 64 | + id-token: write |
20 | 65 | contents: read
|
21 | 66 |
|
| 67 | +concurrency: |
| 68 | + group: std-${{ github.workflow }}-${{ github.ref }} |
| 69 | + cancel-in-progress: true |
| 70 | + |
22 | 71 | jobs:
|
23 | 72 | discover:
|
24 | 73 | outputs:
|
25 | 74 | hits: ${{ steps.discovery.outputs.hits }}
|
26 |
| - nix_conf: ${{ steps.discovery.outputs.nix_conf }} |
27 |
| - |
28 | 75 | runs-on: ubuntu-latest
|
29 |
| - concurrency: |
30 |
| - group: ${{ github.workflow }} |
31 | 76 | steps:
|
32 |
| - - name: Standard Discovery |
33 |
| - uses: divnix/std-action/discover@main |
34 |
| - id: discovery |
| 77 | + # Important: use this as it also detects flake configuration |
| 78 | + - uses: blaggacao/nix-quick-install-action@detect-nix-flakes-config |
| 79 | + # if you want to use nixbuild |
| 80 | + - uses: nixbuild/nixbuild-action@v17 |
35 | 81 | with:
|
36 |
| - github_pat: ${{ secrets.HUB_PAT }} |
| 82 | + nixbuild_ssh_key: ${{ secrets.SSH_PRIVATE_KEY }} |
| 83 | + generate_summary_for: job |
| 84 | + # significantly speeds up things in small projects |
| 85 | + - uses: DeterminateSystems/magic-nix-cache-action@main |
| 86 | + - uses: divnix/std-action/discover@main |
| 87 | + id: discovery |
37 | 88 |
|
38 |
| - build-packages: &run-job |
| 89 | + build: &job |
39 | 90 | needs: discover
|
| 91 | + name: ${{ matrix.target.jobName }} |
| 92 | + runs-on: ubuntu-latest |
| 93 | + if: fromJSON(needs.discover.outputs.hits).packages.build != '{}' |
40 | 94 | strategy:
|
41 | 95 | matrix:
|
42 | 96 | target: ${{ fromJSON(needs.discover.outputs.hits).packages.build }}
|
43 |
| - name: ${{ matrix.target.cell }} - ${{ matrix.target.name }} |
44 |
| - runs-on: ubuntu-latest |
45 | 97 | steps:
|
46 |
| - - name: Configure AWS Credentials |
47 |
| - uses: aws-actions/configure-aws-credentials@v1-node16 |
| 98 | + # Important: use this as it also detects flake configuration |
| 99 | + - uses: blaggacao/nix-quick-install-action@detect-nix-flakes-config |
| 100 | + # if you want to use nixbuild |
| 101 | + - uses: nixbuild/nixbuild-action@v17 |
48 | 102 | with:
|
49 |
| - role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role |
50 |
| - aws-region: us-east-2 |
| 103 | + nixbuild_ssh_key: ${{ secrets.SSH_PRIVATE_KEY }} |
| 104 | + generate_summary_for: job |
| 105 | + - uses: DeterminateSystems/magic-nix-cache-action@main |
51 | 106 | - uses: divnix/std-action/run@main
|
52 |
| - with: |
53 |
| - extra_nix_config: | |
54 |
| - ${{ needs.discover.outputs.nix_conf }} |
55 |
| - json: ${{ toJSON(matrix.target) }} |
56 |
| - # optional: |
57 |
| - github_pat: ${{ secrets.HUB_PAT }} |
58 |
| - nix_key: ${{ secrets.NIX_SECRET_KEY }} |
59 |
| - nix_ssh_key: ${{ secrets.NIXBUILD_SSH }} |
60 |
| - cache: s3://nix?endpoint=sfo3.digitaloceanspaces.com |
61 |
| - builder: ssh-ng://eu.nixbuild.net |
62 |
| - ssh_known_hosts: "eu.nixbuild.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM" |
63 |
| - |
64 |
| - build-devshells: |
65 |
| - <<: *run-job |
| 107 | + |
| 108 | + images: |
| 109 | + <<: *job |
| 110 | + needs: [discover, build] |
| 111 | + if: fromJSON(needs.discover.outputs.hits).oci-images.publish != '{}' |
66 | 112 | strategy:
|
67 | 113 | matrix:
|
68 |
| - target: ${{ fromJSON(needs.discover.outputs.hits).devshells.build }} |
69 |
| - |
70 |
| - publish-containers: |
71 |
| - <<: *run-job |
| 114 | + target: ${{ fromJSON(needs.discover.outputs.hits).oci-images.publish }} |
| 115 | + |
| 116 | + deploy: |
| 117 | + <<: *job |
| 118 | + needs: [discover, images] |
| 119 | + environment: |
| 120 | + name: development |
| 121 | + url: https://my.dev.example.com |
| 122 | + if: fromJSON(needs.discover.outputs.hits).deployments.apply != '{}' |
72 | 123 | strategy:
|
73 | 124 | matrix:
|
74 |
| - target: ${{ fromJSON(needs.discover.outputs.hits).containers.publish }} |
| 125 | + target: ${{ fromJSON(needs.discover.outputs.hits).deployments.apply }} |
75 | 126 | ```
|
76 | 127 |
|
77 |
| -## Notes & Explanation |
| 128 | +</details> |
78 | 129 |
|
79 |
| -### Notes on the Build Matrix |
| 130 | +### Persistent Discovery Host |
80 | 131 |
|
81 |
| -Hits from the discovery phase are namespaced by Block and Action. |
| 132 | +#### Requirements |
82 | 133 |
|
83 |
| -That means: |
| 134 | +- `nix` >= v2.16.1 |
| 135 | +- `zstd` |
| 136 | +- (gnu) `parallel` |
| 137 | +- `jq` |
| 138 | +- `base64` |
| 139 | +- `bash` > v5 |
84 | 140 |
|
85 |
| -- In: `target: ${{ fromJSON(needs.discover.outputs.hits).packages.build }}` |
86 |
| - - `packages` is the name of a Standard Block |
87 |
| - - `build` is the name of an Action of that Block |
| 141 | +The persistent host must also implement the `nixConfig` detection capabilities |
| 142 | +implemented by [this script][script]. |
88 | 143 |
|
89 |
| -This example would be defined in `flake.nix` as such |
| 144 | +[script]: https://github.com/nixbuild/nix-quick-install-action/blob/5752d21669438be20da4de77327ae963e98c82a3/read-nix-config-from-flake.sh |
90 | 145 |
|
91 | 146 | ```nix
|
92 | 147 | {
|
93 | 148 | /* ... */
|
94 | 149 | outputs = {std, ...}@inputs: std.growOn {
|
95 | 150 | /* ... */
|
96 | 151 | cellBlocks = with std.blockTypes; [
|
97 |
| - (installables "packages" {ci.build = true;}) |
98 |
| - (containers "containers" {ci.publish = true;}) |
| 152 | + (devshells "envs" {ci.build = true;}) |
| 153 | + (containers "oci-images" {ci.publish = true;}) |
99 | 154 | ];
|
100 | 155 | /* ... */
|
101 | 156 | };
|
102 | 157 | }
|
103 | 158 | ```
|
104 | 159 |
|
105 |
| -An example schema of the json returned by the dicovery phase: |
| 160 | +<details><summary><h4>GH Action file</h4></summary> |
106 | 161 |
|
107 |
| -```json |
108 |
| -{ |
109 |
| - "containers": { |
110 |
| - "publish": [ |
111 |
| - { |
112 |
| - "action": "publish", |
113 |
| - "actionDrv": "/nix/store/6b0i2ww5drcdfa6hgxijx39zbcq57rwl-publish.drv", |
114 |
| - "actionFragment": "\"__std\".\"actions\".\"x86_64-linux\".\"_automation\".\"containers\".\"vscode\".\"publish", |
115 |
| - "block": "containers", |
116 |
| - "blockType": "containers", |
117 |
| - "cell": "_automation", |
118 |
| - "name": "vscode", |
119 |
| - "targetDrv": "/nix/store/4hs8x5lgb9nkvjfrxj7azv95hi77avxn-image-std-vscode.json.drv", |
120 |
| - "targetFragment": "\"x86_64-linux\".\"_automation\".\"containers\".\"vscode\"" |
121 |
| - } |
122 |
| - ] |
123 |
| - }, |
124 |
| - "devshells": { |
125 |
| - "build": [ |
126 |
| - { |
127 |
| - "action": "build", |
128 |
| - "actionDrv": "/nix/store/zmlva6xlngzj098znyy47p72rxjzgka3-build.drv", |
129 |
| - "actionFragment": "\"__std\".\"actions\".\"x86_64-linux\".\"_automation\".\"devshells\".\"default\".\"build", |
130 |
| - "block": "devshells", |
131 |
| - "blockType": "devshells", |
132 |
| - "cell": "_automation", |
133 |
| - "name": "default", |
134 |
| - "targetDrv": "/nix/store/xq4sl7pf51gp0a036garz56kkr160n5c-Standard.drv", |
135 |
| - "targetFragment": "\"x86_64-linux\".\"_automation\".\"devshells\".\"default\"" |
136 |
| - } |
137 |
| - ] |
138 |
| - }, |
139 |
| - "packages": { |
140 |
| - "build": [ |
141 |
| - { |
142 |
| - "action": "build", |
143 |
| - "actionDrv": "/nix/store/l4y4gzpgym5wbvn42avsaf24nqj0d27y-build.drv", |
144 |
| - "actionFragment": "\"__std\".\"actions\".\"x86_64-linux\".\"std\".\"packages\".\"adrgen\".\"build", |
145 |
| - "block": "packages", |
146 |
| - "blockType": "installables", |
147 |
| - "cell": "std", |
148 |
| - "name": "adrgen", |
149 |
| - "targetDrv": "/nix/store/mwidj7li8b7zypq83ap0fmmwxqx58qn6-adrgen-2022-08-08.drv", |
150 |
| - "targetFragment": "\"x86_64-linux\".\"std\".\"packages\".\"adrgen\"" |
151 |
| - } |
152 |
| - ] |
153 |
| - } |
154 |
| -} |
| 162 | +```yaml |
| 163 | +# yq '. | explode(.)' this.yml > .github/workflows/std.yml |
| 164 | +name: CI/CD |
| 165 | +
|
| 166 | +on: |
| 167 | + pull_request: |
| 168 | + branches: |
| 169 | + - main |
| 170 | + push: |
| 171 | + branches: |
| 172 | + - main |
| 173 | +
|
| 174 | +env: |
| 175 | + DISCOVERY_USER_NAME: gha-runner |
| 176 | + DISCOVERY_KNOWN_HOSTS_ENTRY: "10.10.10.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEOVVDZydvD+diYa6A3EtA3WGw5NfN0wv7ckQxa/fX1O" |
| 177 | +
|
| 178 | +permissions: |
| 179 | + id-token: write |
| 180 | + contents: read |
| 181 | +
|
| 182 | +concurrency: |
| 183 | + group: ${{ github.sha }} |
| 184 | + cancel-in-progress: true |
| 185 | +
|
| 186 | +jobs: |
| 187 | + discover: |
| 188 | + outputs: |
| 189 | + hits: ${{ steps.discovery.outputs.hits }} |
| 190 | + runs-on: [self-hosted, discovery] |
| 191 | + steps: |
| 192 | + - name: Standard Discovery |
| 193 | + uses: divnix/std-action/discover@main |
| 194 | + id: discovery |
| 195 | + # avoids transporting derivations via GH Cache |
| 196 | + with: { ffBuildInstructions: true } |
| 197 | +
|
| 198 | + image: &run-job |
| 199 | + needs: discover |
| 200 | + strategy: |
| 201 | + fail-fast: false |
| 202 | + matrix: |
| 203 | + target: ${{ fromJSON(needs.discover.outputs.hits).oci-images.publish }} |
| 204 | + if: fromJSON(needs.discover.outputs.hits).oci-images.publish != '{}' |
| 205 | + name: ${{ matrix.target.jobName }} |
| 206 | + runs-on: ubuntu-latest |
| 207 | + steps: |
| 208 | + # sets up ssh credentials for `ssh discovery ...` |
| 209 | + - uses: divnix/std-action/setup-discovery-ssh@main |
| 210 | + with: |
| 211 | + ssh_key: ${{ secrets.SSH_PRIVATE_KEY_CI }} |
| 212 | + user_name: ${{ env.DISCOVERY_USER_NAME }} |
| 213 | + ssh_known_hosts_entry: ${{ env.DISCOVERY_KNOWN_HOSTS_ENTRY }} |
| 214 | + - uses: divnix/std-action/run@main |
| 215 | + # avoids retreiving derivations via GH Cache and uses `ssh discovery ...` instead |
| 216 | + with: { ffBuildInstructions: true } |
| 217 | + |
| 218 | + build: |
| 219 | + <<: *run-job |
| 220 | + strategy: |
| 221 | + matrix: |
| 222 | + target: ${{ fromJSON(needs.discover.outputs.hits).envs.build }} |
| 223 | + if: fromJSON(needs.discover.outputs.hits).envs.build != '{}' |
155 | 224 | ```
|
| 225 | +
|
| 226 | +</details> |
| 227 | +
|
| 228 | +## Notes & Explanation |
| 229 | +
|
| 230 | +### Notes on the Build Matrix |
| 231 | +
|
| 232 | +Hits from the discovery phase are namespaced by Block and Action. |
| 233 | +
|
| 234 | +That means: |
| 235 | +
|
| 236 | +- In: `target: ${{ fromJSON(needs.discover.outputs.hits).packages.build }}` |
| 237 | + - `packages` is the name of a Standard Block |
| 238 | + - `build` is the name of an Action of that Block |
| 239 | + |
| 240 | +### Debugging |
| 241 | + |
| 242 | +Watch out for `base64`-encoded blobs in the logs, you can inspect the |
| 243 | +working data of that context by doing: `base64 -d <<< copy-blob-here | jq`. |
0 commit comments