|
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